Skip to main content
GameDev.net gamedev.net
🔒 Locked

Rendering structure

Started by hick18 Jan 30, 2010 at 2:52 AM 21 replies 4k views
Original Post
hick18
hick18
Im looking at my code, and Im thinking that a lot of things know about stuff they dont need to know about, and Im wondering how I can improve upon it, or if its not really a problem. Id like your opinions. At the moment I hava a Renderer Object, whos job it is to carry out all debug drawing tasks, such as DrawLine, DrawBox, DrawSphere etc etc. My game is a bit bare bones at the moment, and I only have:- -Terrain rendering -Ocean rendering -Skybox rendering -Static Mesh rendering (static meshes loaded as models from file) -Dynamic Mesh rendering (dynmaic meshes loaded as skinned models from file) And all of the above have their own HLSL file, so each has its own effect object for rendering, and knows how to create themselves from scratch. So all objects know how to render themselves, which means they know about, and how to use:- - How to load a HLSL file, (effect object) - How to load textures - How to read files - Shader Parameters - etc etc The exception to the above is Static and dynamic meshes, they dont know how to load themselves from file, and dont know how to render themselves. They purely contain only the information they NEED to know about. Such as their materials, textures and vertex/index buffers. But they dont know how to load any of it. This is achieved by having a meshmanager that loads static and dynamic files, and returns them as objects. delegating all the file laoding, such as textures, materials and the mesh file format to the meshmanager. The same applies for the renderer, static and dynamic meshes dont know how to render themselves or anything about shaders what so ever, but they can queue themselves by calling an AddToQueue from the renderer object. Where the renderer doesnt know how to render them either, but has 2 objects, a StaticMeshRenderer and a DynamicMeshRenderer. When addToQueue is called, the renderer delegates the call to one of these, where the object is added to their queue. Once the general Render() is called on renderer, it then in turn calls StaticMeshRenderer.Render() and DynamicMeshRenderer().Render(), where they each render their lists in one go. So to sum it up - Static and Dynamic meshes are loaded using the meshmanager, which delegates the call to staticLoader and dynamicLoader classes - Static and Dynamic meshes are rendered using the renderer, which delegates the call to staticRenderer and dynamicRenderer classes Now, getting back to the question. The other objects (Terrain, Ocean, skybox) dont do any of this, and do everything in one class. Which as I said before, means that an object of each type knows about a lot of stuff it doesnt need to. How do I go about resolving this? Should I follow the same approach and have :- Terrain - Object, contains only terrain paramters and general functions TerrainLoader - Knows how to load a terrain from file, and its dependancies, such as textures and materials TerrainRenderer - Encapsulates the Effect, so shader paramters and techniques. Held by the Renderer, and gets called if a Terrain is in the Render Queue. and the same for the other 2? Im thinking about having the Renderer, work something like this

void Renderer::AddToQueue(Const Actor* pActor)
{

	// ...perform any culling

	//...then

	switch( pActor->GetType() )
	{
		case STATIC_MESH:
			m_pStaticMeshRenderer->AddToQueue( pActor );
			break;

		case DYNAMIC_MESH:
			m_pDynamicMeshRenderer->AddToQueue( pActor );
			break;

		case SKYBOX:
			m_pSkyboxRenderer->AddToQueue( pActor );
			break;

		case TERRAIN:
			m_pTerrainRenderer->AddToQueue( pActor );
			break;

		case OCEAN:
			m_pOceanRenderer->AddToQueue( pActor );
			break;
	}
   
}

void Renderer::Render()
{
	//render all objects
	// ... order dependant? (transparency?)
}


apatriarca
apatriarca
I personally don't like your design. First of all I think that letting the objects know how to render themselves make impossible to batch the objects together. I also think you probably don't mix those objects together in your code, so you can probably have several different functions which deals with a single actor type instead of a single function which need to query the type of every actor.
hick18
hick18
Im not sure you understood my post. I do batch my rendering, hence the add to queue functions. And the only objects that know how to draw themselves, were the skybox, ocean and terrain.

I know that the switch statement is kind of anti OO, but at some point, objects have to be rendered, and depending on the objects renderable type, its going to need to be rendered a different way, and added to the right queue.

The way I plan to have this, is something like

enum RType { StaticMesh, DynamicMesh, Terrain, Ocean, Skybox };

class RenderView
{
RenderType r_Type;
void* p_RenderObject;
}

class Actor
{
// ... other data
RenderView* m_pRView;
// ... other data
}

This way, it will hopefully allow me to have an object factory that correctly builds objects, and sets them up with the correct RenderView data. The only system that cares what p_Render is, is the renderer. It can determine what sort of object it is from RenderType, and then cast the p_Render variable to the appropiate type. Which will allow it to access the data that was set by the factory. The renderer can then add it to the correct queue, where the queues are rendered at the end of the frame in batches by the appropiate renderer.

apatriarca
apatriarca
I understand what's your motivation behind that switch statement but I think those objects are so different that they are probably stored in different data structures or they can be at least easily separated. So in my head it makes more sense to simply have a function for each type of object.
hick18
hick18
Im not sure what you mean by different function for each object. Do you mean something like this?

Renderer->AddStaticMeshToQueue(pObject);
Renderer->AddDynamicMeshQueue(pObject);
Renderer->AddOceanToQueue(pObject);
Renderer->AddSkyboxToQueue(pObject);
Renderer->AddTerrainToQueue(pObject);

Well, thats what Im trying to avoid. The code that adds something to be rendered shouldn't realy care what that objects renderable object is.

For example, my object factory will return an object, and the code that calls the object factory shouldnt care what the renderable object is, it just knows that if it should be rendered or not

// spawn an ActorActor* pActor = ObjectFactory::Create( SomeObjectType );Renderer->AddToQueue( pActor );Physics->AddToWorld( pActor );//... etc etc


or maybe the above 2 lines could be done in the ObjectFactory itself, depending on some paramters.

// spawn an ActorActor* pActor = ObjectFactory::Create( SomeObjectType, Params );


As you can see, Im still experimenting :)
apatriarca
apatriarca
The main problem I have with your design is probably that I think a factory like yours is completely useless. To make something useful with an object you generally have to know what is its type or make a virtual function in the parent class. So, you create every object without taking care of their type, but you always need a switch to use them. I prefer to have several factories, each one creating objects which can be manipulated in the same way.
freeworld
freeworld
I believe what he's trying to say about having different functions and factories is simply instead of using just the addobject() function you would do

renderer->addStaticMesh()

and to create a mesh you would do

StaticMeshManager->Create( object*& )

with that you can still combine those managers into your renderer, but your renderer really doesn't need to deal with loading resources.

Quote:
Original post by apatriarca
The main problem I have with your design is probably that I think a factory like yours is completely useless. To make something useful with an object you generally have to know what is its type or make a virtual function in the parent class. So, you create every object without taking care of their type, but you always need a switch to use them. I prefer to have several factories, each one creating objects which can be manipulated in the same way.


What he's saying here is a great point to, you the programmer know what you're trying to add to the queue and what queue your trying to add it to so why generalize it so much by forcing a generic type like the object is.

Also I prefer to add draw calls to the renderer rather than objects, think of it as just the info that is needed to draw what you want to the screen, not a full on pointer to what your drawing.

Krohm
Krohm
I think your renderer is too much high-level. Let's be serious DrawBox? DrawSphere? I am moderately sure a long-span renderer should never, ever provide those functions, not even for debugging purposes. DrawLine is especially troublesome since lines are, in my opinion, severely broken in 3D... but I hope there are only (bad) examples.

Terrain-Ocean-Skybox.
I don't quite understand why the renderer should ever care about this. Maybe I'm thinking a bit too low-level but I don't see why a renderer should have to deal with them explicitly.
Example: terrain. I expect this to have a relatively simple shading and to count tons of tris. Culling the terrain seems a better fit for the scenegraph. The resulting terrain cells are just vertices.
Example: ocean. Probably a morphing terrain (VS-animated?) with added reflections. Basically a sequence of preparation passes and vertices to draw yet again.
Example: skybox. Probably easy to shade, low polycount.
Suppose you're going to add a "see-thru portal", are you going to add another resource? What about mirrors? I'm unsure I know a renderer which is so much high-level, somewhat taking the place of the scenegraph.

I'm not even completely sold on the idea of having a render-queue in the first place. Again, call me old-schooled, but I would expect a thing called 'renderer' to deal mainly with low-level stuff such as vertex buffers, draw calls and such. I would check again for similarities between the various usage cases.

Just as apatriarca says, there's no need to switch on resource type like that. Either your resources are in the same low-level rendering mode (and thus are essentially the same thing with different values) or they aren't (which then implies mixing them is probably going too far).

Quote:
Original post by hick18
I know that the switch statement is kind of anti OO, but at some point, objects have to be rendered, and depending on the objects renderable type, its going to need to be rendered a different way, and added to the right queue.
Wrong, for various reasons. First, the switch statement isn't about messing OO, it is about messing with meaningful design. I truly don't understand why you think skybox/ocean/terrain use cases are so different to be promoted to first-class renderer citizens. Please elaborate.
Second, regardless of what you've been told at school or by your friends, or kept reading on books, OO tends to break when dealing with rendering. That happens because at low-level you have primitive operations like bind shader, set ibo, set vbo, select render-target, etc where the object being affected is the 3d device. You can try to put the low-level elaboration the further down the pipe as you can but if this object is called 'renderer', I would expect it to abstract the renderer, or the API, but not much more (I am reiterating the concept here on purpose), and let more elaborate management to an higher level 'renderer-driver' or 'renderer-manager'.
Previously "Krohm"
hick18
hick18
Quote:
The main problem I have with your design is probably that I think a factory like yours is completely useless. To make something useful with an object you generally have to know what is its type or make a virtual function in the parent class. So, you create every object without taking care of their type, but you always need a switch to use them. I prefer to have several factories, each one creating objects which can be manipulated in the same way.


Hmm, I think Im not fully explaining the point Im trying to get across. When using an object, lets say an Enemy object, where its type derives from an actor class. In different parts of the game engine code, I dont see why different sections need to know about all the details of each member of the actor class.

For example, why would the game logic care how that enemy is to be rendered? I dont think it would. So by having some RenderView object within the actor type, I can hide the renderering details from other parts of the engine. As only the renderer knows how to turn that RenderView into something meaningfull. As far as other parts of the game engine are concerned, there is a pointer within the actor class called RenderView, and thats all they know, they dont know of care how to use it, because I dont think they should.

Quote:
Also I prefer to add draw calls to the renderer rather than objects, think of it as just the info that is needed to draw what you want to the screen, not a full on pointer to what your drawing.


Its really not that simple though. The way a terrain is rendered and stored is probaly completly different to that of how an ocean is rendered and stored. Its not simple a case of passing across a few buffers and materials. The Specific shaders and shader constants need to be set up. And these constants are going to be different when rendering different things. For example the information a terrain has is going to be vastly different to the information a skybox has. I dont see how you can generalize these kind of calls. Its makes more sense in my mind to have specific effects that can be called, with objects that a specific effect works with.



Quote:
I think your renderer is too much high-level. Let's be serious DrawBox? DrawSphere? I am moderately sure a long-span renderer should never, ever provide those functions, not even for debugging purposes. DrawLine is especially troublesome since lines are, in my opinion, severely broken in 3D... but I hope there are only (bad) examples.


These are simply convenience functions, and as said above, are used for nothing other than debugging. Their performance is not a majour concern. I really dont see a problem with these functions, but am interested in hearing other techniques of how you would plan to do your debug drawing.

Quote:

Terrain-Ocean-Skybox.
I don't quite understand why the renderer should ever care about this. Maybe I'm thinking a bit too low-level but I don't see why a renderer should have to deal with them explicitly.
Example: terrain. I expect this to have a relatively simple shading and to count tons of tris. Culling the terrain seems a better fit for the scenegraph. The resulting terrain cells are just vertices.
Example: ocean. Probably a morphing terrain (VS-animated?) with added reflections. Basically a sequence of preparation passes and vertices to draw yet again.
Example: skybox. Probably easy to shade, low polycount.
Suppose you're going to add a "see-thru portal", are you going to add another resource? What about mirrors? I'm unsure I know a renderer which is so much high-level, somewhat taking the place of the scenegraph.


As mentioned before, there are specific techniques for rendering each of these, as they are optimised differently in their own ways. For example I have ocean and terrain effect files, which are completly different, and need vastly different constants set up before they are used to draw with. The reason I have the render care about these, is that the renderer knows how to set these effects up before using them. The renderer takes care of managing and calling sub renderers,

TerrainRenderer
OceanRenderer
etc etc

The renderer takes in info about the Terrain and Ocean objects, and passes that on to the sub renderers above to set up the effect and draw with. They key part here is that only the renderer knows how to read the actors RenderView object as something meaningful, no other part of the engine can do this. As I dont see why any other part of the engine should know about or how do the above steps. If I have an object that needs renderering, I think its a fairly good approach to be able to pass that object on to the renderer, where the renderer finds and sets up the correct effect, by finding out exactly what that object is, and then rendering it, preferbly by batching it.

From my understanding, your approach is something like

Renderer->SetIndexBuffer(pTerrain->GetIndexBuffer());
Renderer->SetVertexBuffer(pTerrain->GetVertexBuffer());
Renderer->SetMaterials(pTerrain->GetMaterials());
Renderer->SetCamera(pCam);
Renderer->Draw();

For a simple tri grid based terrain, this may work fine. But my terrain is far from that.

Quote:
I'm not even completely sold on the idea of having a render-queue in the first place. Again, call me old-schooled, but I would expect a thing called 'renderer' to deal mainly with low-level stuff such as vertex buffers, draw calls and such. I would check again for similarities between the various usage cases.


Yes I would agree with that, I think we do have different ideas of what a renderer should and and shouldnt do. In my mind, a renderer should take in :-

- some object, which holds some general data and some specific effect compliant data,
- the specific effect reference to be used to render with, ie how the user wants that to be rendered, and then perform the drawing.

I dont claim that any of the above is the best way to it, Im just elborating on my thoughts as you asked, and greatly appreciate your thoughts on it too.
Krohm
Krohm
Quote:
Original post by hick18
Its really not that simple though. The way a terrain is rendered and stored is probaly completly different to that of how an ocean is rendered and stored. Its not simple a case of passing across a few buffers and materials. The Specific shaders and shader constants need to be set up. And these constants are going to be different when rendering different things. For example the information a terrain has is going to be vastly different to the information a skybox has. I dont see how you can generalize these kind of calls. Its makes more sense in my mind to have specific effects that can be called, with objects that a specific effect works with.
Personally I don't think so. You'll have some shaders to bind and they'll need their parameters. There's probably going to be at least a ibo/vbo pair and a sequence of draw calls. I see the differences, but it's not "vastly different" at high-level. I don't see it 'vastly different' even at low-level.
Quote:
Original post by hick18
These are simply convenience functions, and as said above, are used for nothing other than debugging. Their performance is not a majour concern. I really dont see a problem with these functions, but am interested in hearing other techniques of how you would plan to do your debug drawing.
I start by ensuring the renderer doesn't get wrong values - debugging all other components. If this fails, I use NVPerfHUD. If this fails, I use PIX. If this fails, I use well-known meshes going thuru similar data paths (including similar loading and similar used components). Recall that my renderer is low-level and thus gets much less chances to go awry.
Quote:
Original post by hick18
As mentioned before, there are specific techniques for rendering each of these, as they are optimised differently in their own ways...
The renderer takes in info about the Terrain and Ocean objects, and passes that on to the sub renderers above to set up the effect and draw with. They key part here is that only the renderer knows how to read the actors RenderView object as something meaningful, no other part of the engine can do this. As I dont see why any other part of the engine should know about or how do the above steps. If I have an object that needs renderering, I think its a fairly good approach to be able to pass that object on to the renderer, where the renderer finds and sets up the correct effect, by finding out exactly what that object is, and then rendering it, preferbly by batching it.
It sounds sort of FFP-ish to me. Essentially, you're taking some shaders (engine-shaders here) and promote them to be more important than others. I still don't see how binding the 'ocean' shader (API shader) and fetching its uniforms can be considered massively different from binding and fetching the 'terrain' shader. You could start again elaborating what do you expect from your terrain or ocean shaders. Maybe it's worth to clearly mark engine terms and API terms - for example, I often reference my 'engine shaders' as 'Appearances' (which are sort of similar to D3D's Effects) and my 'API shaders' as 'Kernels' or 'Shaders' when associated to their expected uniform bindings.
Quote:
Original post by hick18
Yes I would agree with that, I think we do have different ideas of what a renderer should and and shouldnt do. In my mind, a renderer should take in :-

- some object, which holds some general data and some specific effect compliant data,
- the specific effect reference to be used to render with, ie how the user wants that to be rendered, and then perform the drawing.

I dont claim that any of the above is the best way to it, Im just elborating on my thoughts as you asked, and greatly appreciate your thoughts on it too.
Yes, that's fine, but you cannot write "some object". The renderer explicitly has notion of certain types of objects (a low-level one wouldn't have). Either is some object, or a type ocean object, or a type terrain object...

I have the feeling you're trying to keep your secrets for yourself and still get meaningful advice.
Previously "Krohm"
hick18
hick18
Quote:
Personally I don't think so. You'll have some shaders to bind and they'll need their parameters. There's probably going to be at least a ibo/vbo pair and a sequence of draw calls. I see the differences, but it's not "vastly different" at high-level. I don't see it 'vastly different' even at low-level.


Yes, but how do you propose to make that call? Give me an example of how you would set up the rendering of 2 objects, one being a terrain and another an Ocean, ie the code being called each frame, where

- They have different shader paramters
- Different techniques depending on distance from the camera
- Different material types
- Different methods of rendering, ie you may be short on memory, so the terrain could have a small single index/vertex buffer,maybe even 3 LOD levels, which is maybe a 1/16th of the whole terrain size, which gets moved and rendered multiple times to fill the whole terrin size.

How do you plan on generalizing all that? So you can call your low level renderer?
If you could give some example code, it would be easier for me to understand what you mean.

Quote:
I start by ensuring the renderer doesn't get wrong values - debugging all other components. If this fails, I use NVPerfHUD. If this fails, I use PIX. If this fails, I use well-known meshes going thuru similar data paths (including similar loading and similar used components). Recall that my renderer is low-level and thus gets much less chances to go awry.


Not sure what you mean by this. When I said debug rendering I meant as in, examples such as plotting paths for AI to see whats happening, drawing normals of geometry, or draing intersection points of collision models for inspecting.

Quote:
It sounds sort of FFP-ish to me. Essentially, you're taking some shaders (engine-shaders here) and promote them to be more important than others. I still don't see how binding the 'ocean' shader (API shader) and fetching its uniforms can be considered massively different from binding and fetching the 'terrain' shader. You could start again elaborating what do you expect from your terrain or ocean shaders. Maybe it's worth to clearly mark engine terms and API terms - for example, I often reference my 'engine shaders' as 'Appearances' (which are sort of similar to D3D's Effects) and my 'API shaders' as 'Kernels' or 'Shaders' when associated to their expected uniform bindings.


I dont think its fixed function pipeplie like. When im calling a renderTerrain function from the renderer, its binding a new shader, and setting that shader up before rendering. Which is simply doing what you're doing outside of the renderer. My approach is:-

You pass an object to the renderer, other code knows what type of object it is, whether it be a terrain or an ocean, but they dont know how to set the renderer up to render it correctly. Thus when passed to the renderer, the renderer passes it to the correct sub renderer, which does know how.

This way all rendering "stuff" is kept in the renderer code, ie lots of calls are added throughout the frame time to queue rendering tasks, and then they get carried out at some specific time as a batch. How would you do it with your approach? Does your approach allow you to batch lots of similar calls? such as a bunch of static meshes that are all drawn the same way?

Quote:
Yes, that's fine, but you cannot write "some object". The renderer explicitly has notion of certain types of objects (a low-level one wouldn't have). Either is some object, or a type ocean object, or a type terrain object...


You pass an actor to the renderer, and the renderer can figure out which queue to add it to by calling that actors type function, as was described in an earlier post.

Btw, thanks for replying.
freeworld
freeworld
You're making your renderer know too much. What exactly does the renderer render... triangles, to make things easier they can be grouped as vertex/index buffers. What else does it need, materials, shaders, textures and the data pertaining them. Now the data that the shaders and what need should not be provided directly by the renderer.

Here's a very simple drawcall (I know very rudimentary)

struct DRAWCALL
{
VertexBuffer vb;
IndexBuffer ib;
std::list< texture* > textures;
std::list< shaders* > shaders;
Matrix positions and rotations.
};

Your scene graph or something similar could cull your meshes, terrains, and what not, then using what should be drawn, batch it, order it and pop out neatly packed drawcalls.

Once again for shaders and textures, you make another struct that would contain a pointer to the shader and a list of constants or other data that should be passed to the shader.
hick18
hick18
Yes, but where does setting techniques and shader constants come into your approach?

Should the renderer know about the directx effect?
Who should own the effect?

WhilstI agree, my renderer does probaly know more than it should, I think you're simplifyiung things too much. Its usually never a case of just passing a shader and some buffers across.

What if there are a bunch of buffers that need to be chosen carefully before passing to the renderer, or constants that need to be carefully chosen and passed across, where would this code take place?

At what point in the code do you call your renderer with that struct?

If you could explain in more detail.
freeworld
freeworld
Quote:
Original post by hick18
Yes, but where does setting techniques and shader constants come into your approach?

Should the renderer know about the directx effect?
Who should own the effect?


Technically your resource manager would own the effect file, everyone else would just have a handle to it. Make a new struct that just holds a handle to your effect file, and the desired technique if that's necessary, and have an std::list in the struct that holds all the constants that need to be set and to what register they should be set to. When you're renderer goes to render that buffer, it would run through the list and setup the shader that it was given. and set the textures in the order they were given.


Quote:
Original post by hick18
WhilstI agree, my renderer does probaly know more than it should, I think you're simplifyiung things too much. Its usually never a case of just passing a shader and some buffers across.

What if there are a bunch of buffers that need to be chosen carefully before passing to the renderer, or constants that need to be carefully chosen and passed across, where would this code take place?


Decided what buffers to draw is the scengraphs job, it is the one that should know what is in front of the camera, but it shouldn't know how to draw what is there.

Quote:
Original post by hick18
At what point in the code do you call your renderer with that struct?

If you could explain in more detail.


I would make that call in the scenegraph or at least allow the scenegraph to built a list of those calls, since it is the one that knows what's in front of the camera. It would take what's there, batch it by whatever means fit, then it would fill the draw call and pass it to the renderer.
hick18
hick18
Quote:
Technically your resource manager would own the effect file, everyone else would just have a handle to it. Make a new struct that just holds a handle to your effect file, and the desired technique if that's necessary, and have an std::list in the struct that holds all the constants that need to be set and to what register they should be set to. When you're renderer goes to render that buffer, it would run through the list and setup the shader that it was given. and set the textures in the order they were given.


But for the renderer to be able to set up the shader, it would need to know about that shader, and seeing as all shaders are different, ie they have different constants and techniques to set, the renderer would need to know about all shaders. Also, what do you mean by a list of constants? they could be anything, a matrix palette, a list of lights, camera data etc etc. How can I pass this in an std::list? using void pointers?, and more to the point, how is the renderer supposed to set up a shader with this given list? ie given some data member from the draw call struct or that list, how would it know which shader constant it refers to, so it can set it?

Quote:
Decided what buffers to draw is the scengraphs job, it is the one that should know what is in front of the camera, but it shouldn't know how to draw what is there.


How does the scenegraph know what to get from the objects in the graph? For example a terrain could be made of small instanced geometry chunks rendered in a grid. And a skinned mesh would need the mesh buffers and its matrix palletes, how would the scenegraph know to get this, and pack it into a draw call? Another example would be shadowmapping. How would the scenegraph know to pass the lights perspective view as the camera Matrix as opposed to the cameras.

Quote:
I would make that call in the scenegraph or at least allow the scenegraph to built a list of those calls, since it is the one that knows what's in front of the camera. It would take what's there, batch it by whatever means fit, then it would fill the draw call and pass it to the renderer.


If you're going to send each call to the renderer, which consists of a shader, its parameters and states to set. What would be the point in batching these calls? You lose the benefits of batching, which is to only be setting the same shader calls, same shader constants and same states once.

Im really not understanding this idea of a general drawcall. The way I see it is that there are too many differences between rendering techniques to be able to pack them into a single general draw call.
apatriarca
apatriarca
Quote:
Original post by hick18
Im really not understanding this idea of a general drawcall. The way I see it is that there are too many differences between rendering techniques to be able to pack them into a single general draw call.

There is a single way to render something with DirectX or OpenGL APIs. So, why shouldn't you be able to do the same thing in your application? You simply need to pass to the renderer all the informations about the shader to use, the constants and parameters to set, the buffer to use and so on for each object. The objects of your application can for example contains an object which contains these informations and pass it to the renderer when you want to draw it.
hick18
hick18
Yes, I understand the idea. I just dont understand how you are supposed to implement it. For example, how are the problems I discussed in my previous post to be implemented?

You say pass a list of constants, and paramters that the shader is to set, but

- How can you pass a list of constants that are of lots of different types? floats, vectors, matrices, textures, raw data, etc etc

- Once passed the list and the shader, how is the renderer to know how to map each item in the list to the shader constant handles

- If each call will contain a different list of constants to set, as shaders require different constants types and values, who binds them to the shader? if its the renderer, then wont this require the renderer to know about all shaders?

- How does the code creating the draw call know *what* to get from the object that is to be rendered, to construct the draw call?

- If each call contains state data and shader data. Each call will involve shader and state switching. Thus how can you batch calls to optimize. i.e. to reduce shader/state switching?

_the_phantom_
_the_phantom_
Quote:
Original post by hick18
- If each call will contain a different list of constants to set, as shaders require different constants types and values, who binds them to the shader? if its the renderer, then wont this require the renderer to know about all shaders?


It doesn't need to know 'all about shaders', it just needs to know 'here is a shader, here are some paramters' and perform operations on them.

It doesn't care what the shader is, it just sees a handle and some data which is bound. The data will carry with it information on where it needs to be bound if the API requires it (this would be collected at creation time and paired up in some manner).

Quote:

- If each call contains state data and shader data. Each call will involve shader and state switching. Thus how can you batch calls to optimize. i.e. to reduce shader/state switching?

[/quote]

By bucket sorting things so that objects with common properties end up together.

You don't render things as you call 'render'; you collect what needs to be rendered first, sort it as required (by state, by distance from camera) and THEN submit your drawing in the order you have defined.

As for your original post; there is no need for the objects to know the details.
Water for example would be tagged as requiring a water material, part of this might well be a shader program as well as information regarding colour and other details. The 'water', on setup, asks someone for a material containing a perticular shader and textures and sets up any details it requires.

Shader parameters would be abstracted to the point that, for example, your water knows it expects a 'wavyness' paramter in its material, but it doesn't care where it comes from, just that it can set it, maybe read back, and maybe access it via a cached handle to speed up setting the value.

When setting up the draw the data set to the render will be something like;
here is an object, it has X vertices, Y indices and it uses this material.

The rendering backend stores these details, sorting as required, and asks for the next object and so on. At a later point this list of things to be draw is executed; common state is set and objects with that state are drawn. The process repeats itself.

All the renderer needs todo is say 'set this material, bind this data, now draw it'. It doesn't care beyond that what happens under the hood as it will be abstracted away to deal with itself.

material->setCurrent();data->setCurrent();DrawCurrent();


Is one possible setup (excluding loops etc).
freeworld
freeworld
It might make it simpler to understand what I'm getting at if you strip your renderer down to just drawing a simple mesh, with the most basic shader that simply applies the matrix and texture.

Starting with that make your renderer deal with draw calls only, and design a drawcall that can contain the needed data to get you to drawprimitive(). Then work outwards from there in think what more would be needed in the drawcall to facilitate things like variable shader constants, or passing lights params to the shader and so forth.

You might just be thinking about things too big, think what is just the basic parameters your renderer needs.
hick18
hick18
Can I give you an example of one of my rendering techniques? and maybe you could show me the pseudo code for how you would do it with the technique you're suggesting?

My Ocean shader works by projecting a plane at some height above the ground level. To do this it requires paramters to be passed into the shader, which is mainly data relating to the camera frustum, such as:-

cbuffer cbPerObject
{
float2 gNearFar;
float2 gOceanOffset1;
float2 gOceanOffset2;
float2 gOceanOffset3;
float4 gOceanPlane;
}

and then lots of other parameters that define the oceans material attributes. Because these are specific to the ocean shader, how would this data be included into the drawcall object, and finally when all sorting is done as you mentioned, how would these paramters then be bound to the shader when it was time for the ocean to be drawn?

From my understanding, I get something like this. Sorry about the length of the code, but its the implementation details that im having the problem with, so i thought I step up from pseudo code:-

// Scenegraph codevoid Scenegraph::AddToRendererVisibleObjects(){	// Traverse scenegraph	// ...	// Found Ocean Object	DrawCall l_DrawCall;	IRenderable* pRenderable = pSceneGraphNode->GetIRenderable();	int Subsets = pRenderable->GetNumSubsets();	for( int i = 0; i < Subsets; ++i )	{		// Set the shader to be used with this call		l_DrawCall.SetShader(pRenderable->GetSubset(i)->GetShader());		// Set the IndexBuffer to be used with this call		l_DrawCall.SetIBuffer(pRenderable->GetSubset(i)->GetIBuffer());		// Set the VertexBuffer to be used with this call		l_DrawCall.SetVBuffer(pRenderable->GetSubset(i)->GetVBuffer());		// Device paramters (not shader related), Enable Blending? CCW/CW Culling? Enable Depth Testing? Enable Depth Writing?  etc etc		l_DrawCall.SetDeviceParams(pRenderable->GetSubset(i)->GetDeviceParams()); 		// shader constants data		// =========================================================================================			// base material class, derived classes could include Ocean material or terrain material			Material* pMaterial = pRenderable->GetSubset(i)->GetMaterial(pMaterial); 			l_DrawCall.SetMaterial(pMaterial);			// Fixed constants, dont ever change, were set in the Game Engine Tools, and were read in 			// when the ocean object config file exported from the game engine tools was loaded by the 			// game. Ccean shader will know how to cast the void* into a meaningfull data chunk.			void* pFixedConstants = (void*)pRenderable->GetFixedConstants();			l_DrawCall.SetFixedConstants(pFixedConstants);			// Varible constants. Very likly to change. Such as camera position, array of closest			// lights, camera frustum data, ocean wave parameters that are dynamic due to weather			// conditions, is player under water? etc etc.			// How to set this??????		// =========================================================================================		g_pRenderer->QueueDrawCall(l_DrawCall);	}}// Renderer Codevoid Renderer::QueueDrawCall( const DrawCall& drawCall ){	m_DrawCallsQueue.push_back(drawCall);}void Renderer::SortQueuesAndArrangeBuckets(){	// sort queue into buckets	// 1) bucket for each shader	// 2) Translucent objects put into special lastRendering bucket	// ... other rules}void Renderer::RenderAll(){	Renderer::RenderBuckets();}void Renderer::RenderBuckets(){	// Render buckets in turn	// ... Renderer::RenderBucket(m_Bucket[5]);}void Renderer::RenderBucket( const Bucket& bucket ){	// note :  This happens to be the Ocean object buecket call	DeviceParams* pDeviceParams = bucket->GetDeviceParams();	Renderer::SetDeviceParams( pDeviceParams );		// shader is base class for a number of derived classes, inc the ocean shader 	Shader* pShader = g_EffectSystem->GetShader(bucket->GetShader());	// Render calls in turn	// ... Renderer::Render(bucket.GetCall(5), pShader);}void Renderer::Render( const DrawCall& drawCall, const Shader* pShader ){		// note :  This happens to be the Ocean object draw call	// Ocean shader will know how to cast this void pointer to something meaningful	void* pFixedConstants = (void*)drawCall->GetFixedConstants();	pShader->BindFixedConstants(pFixedConstants);	// Ocean shader will cast this to a specialized ocean material		Material* pMaterial = drawCall->GetMaterial();	pShader->BindMaterial(pMaterial);		IBuffer* pIBuffer = drawCall->GetIBuffer();	Renderer::BindIBuffer(pIBuffer);		VBuffer* pVBuffer = drawCall->GetVBuffer();	Renderer::BindVBuffer(pVBuffer); 	Renderer::Draw();}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.