PDA

View Full Version : Scene Management



technomage
25-10-2005, 10:41 PM
I've been thinking about scene management allot recently. My latest probject has quite a few objects active at one time, and even with Frutsum culling and a fairly low poly count (compared to Quake 3 etc) i only manage between 30 and 40 frames per second.

I have attributed this to the way in which I'm rendering the objects. Currently I render each object individually , in other works for each object, I set the vertex array, normal and tex coord pointers. Then for each mesh in the model I set the materials and then the texture then render using glDrawElements.

With this informationin mind I have come to the conclusion that this method sucks. I am repeatedly reloading the textures, and resetting the vertex data, even if multiple objects use the same mesh (which they do).

Now I've started thinking about a more advanced rendering technique such as the one desribed over at delphi3d.net (http://www.delphi3d.net/articles/printarticle.php?article=stateman.htm), but I was wondering if anyone else had any different approaches to this issue :?:

Sly
26-10-2005, 03:19 AM
What we use is a distance-based and frustum-culled approach.

Is the object within a specified maximum distance of the camera?
Is any part of the bounding box of the object within the view frustum?
If yes to both of the above, draw the object a sub-mesh at a time sorted by material.

Paulius
26-10-2005, 06:39 AM
My objects don?¢_Tt know how to draw themselves and just contain their vertex and material data, when they are supposed to be drawn that data is passed to a separate scene manager which if that data passes a frustum check adds it to the what should be drawn list. Later before drawing that list is roughly sorted by distance to the camera and thoroughly by material. Setting materials often should definitely be avoided, especially changing textures, setting vertex pointers not that much, you?¢_Tre unlikely to have many static meshes in the scene anyway.

Sly
26-10-2005, 07:06 AM
If the object is fully opaque, sort them from closest to furthest and draw in that order. It is quicker to draw this way because the depth-test will fail more often than not and it won't even try to draw the pixel.

If the mesh contains materials that are translucent, you should sort from furthest to closest and draw in that order. This is to get those materials looking correct so you can see further objects through closer objects.

technomage
27-10-2005, 10:55 AM
So the brute force approach isn't a good option. Any idea what kind of performance increases you get by reducing the amount of state change
How did you find out the order of state changes from most expsenive to least? I know changing textures is expensive, but after that, I'll be changing material colors, blending functions etc.

Anyonme got any links on where to find this information out :?:

Sly
27-10-2005, 01:10 PM
I've learnt it over time, reading bits and pieces here and there, talking to fellow coders, etc.

This is my Delphi conversion of a render state manager that was originally found on a GameDev.Net forum. What it does is compare the block of states that you want to change with the states that it knows are already set, and only changes the states necessary. Note it does not work with a pure device, because you cannot query a pure device for the current state.

unit DonkRenderStateManager;

interface

{
RenderStateManager.h - John Dexter - 21/6/2005

This class implements functionality to do 2 things:
1)Maintain the D3D render & texture stage states in a reliable way
2)Reduce the number of D3D Renderstate and TextureStagestate changes
}

uses
Direct3D9, Windows;

const
NUM_TEXTURE_STAGES = 8;
MAX_RENDER_STATE_STACK_DEPTH = 16;
MAX_RENDER_STATE_STACK_SIZE = 256;
MAX_TEXTURE_STAGE_STATE_STACK_DEPTH = 16;
MAX_TEXTURE_STAGE_STATE_STACK_SIZE = 256;

type
PDKRenderState = ^TDKRenderState;
TDKRenderState = record
State: TD3DRenderStateType;
Value: DWord;
end;

PDKTextureStageState = ^TDKTextureStageState;
TDKTextureStageState = record
Stage: DWord;
State: TD3DTextureStageStateType;
Value: DWord;
end;

PDKStackIndex = ^TDKStackIndex;
TDKStackIndex = record
Start: Cardinal;
Count: Cardinal;
end;

TDKRenderStateManager = class
private
FDevice: IDirect3DDevice9; // The D3D Device to set the states on
FRenderStates: array [TD3DRenderStateType] of DWord; // The current set of D3D render states which are required
FTextureStageStates: array [0..NUM_TEXTURE_STAGES - 1, TD3DTextureStageStateType] of DWord; // The current set of D3D texture stage states which are required for each stage

FRenderStateStackDepth: Cardinal;
FRenderStateStackIndexes: array [0..MAX_RENDER_STATE_STACK_DEPTH - 1] of TDKStackIndex;
FRenderStateStack: array [0..MAX_RENDER_STATE_STACK_SIZE - 1] of TDKRenderState;

FTextureStageStateStackDepth: Cardinal;
FTextureStageStateStackIndexes: array [0..MAX_TEXTURE_STAGE_STATE_STACK_DEPTH - 1] of TDKStackIndex;
FTextureStageStateStack: array [0..MAX_TEXTURE_STAGE_STATE_STACK_SIZE - 1] of TDKTextureStageState;

FNumRenderStateCalls: Cardinal;
FNumRenderRedundantStateCalls: Cardinal;
FNumTextureStageStateCalls: Cardinal;
FNumTextureStageRedundantStateCalls: Cardinal;
FNumConsecutiveRenderPushes: Cardinal;
FNumConsecutiveTextureStagePushes: Cardinal;
public
constructor Create(ADevice: IDirect3DDevice9);
{ pass in a set of states and their desired values. The current values for these
set of states are recorded, and the new values applied }
procedure PushRenderStateBlock(Block: PDKRenderState; Count: Cardinal);
{ gets the last set of states set, and restores the previous values }
procedure PopRenderStateBlock;
{ pass in a set of stage states and their desired values. The current values for these
set of states are recorded, and the new values applied }
procedure PushTextureStageStateBlock(Block: PDKTextureStageState; Count: Cardinal);
{ gets the last set of states set, and restores the previous values }
procedure PopTextureStageStateBlock;
procedure BeginScene;
procedure EndScene;
end;

implementation

{ TDKRenderStateManager }

constructor TDKRenderStateManager.Create(ADevice: IDirect3DDevice9);
var
RST: TD3DRenderStateType;
TST: TD3DTextureStageStateType;
Stage: Integer;
begin
FDevice := ADevice;

for RST := Low(TD3DRenderStateType) to High(TD3DRenderStateType) do
FDevice.GetRenderState(RST, FRenderStates[RST]);

for TST := Low(TD3DTextureStageStateType) to High(TD3DTextureStageStateType) do
for Stage := 0 to NUM_TEXTURE_STAGES - 1 do
FDevice.GetTextureStageState(Stage, TST, FTextureStageStates[Stage, TST]);

FRenderStateStackDepth := 0;
FTextureStageStateStackDepth := 0;
end;

{ pass in a set of states and their desired values. The current values for these
set of states are recorded, and the new values applied }
procedure TDKRenderStateManager.PushRenderStateBlock(Block: PDKRenderState; Count: Cardinal);
var
Index: Cardinal;
StackIndex: PDKStackIndex;
RS: PDKRenderState;
begin
Assert(FRenderStateStackDepth < MAX_RENDER_STATE_STACK_DEPTH - 1);
Index := 0;
if FRenderStateStackDepth > 0 then
begin
StackIndex := @FRenderStateStackIndexes[FRenderStateStackDepth - 1];
Index := StackIndex^.Start + StackIndex^.Count;
end;
Inc(FRenderStateStackDepth);
StackIndex := @FRenderStateStackIndexes[FRenderStateStackDepth - 1];
StackIndex^.Start := Index;
StackIndex^.Count := 0;

RS := Block;
//build a list of the current values for each state which is to change
while Count > 0 do
begin
if RS^.Value <> FRenderStates[RS^.State] then
begin
Assert(Index < MAX_RENDER_STATE_STACK_SIZE - 1);
FRenderStateStack[Index].State := RS^.State;
FRenderStateStack[Index].Value := FRenderStates[RS^.State];
Inc(Index);
StackIndex^.Count := Index - StackIndex^.Start;
//set and record the new value for this state
FRenderStates[RS^.State] := RS^.Value;
FDevice.SetRenderState(RS^.State, RS^.Value);
end
else
begin
Inc(FNumRenderRedundantStateCalls);
end;
Dec(Count);
Inc(FNumRenderStateCalls);
Inc(RS);
end;

Inc(FNumConsecutiveRenderPushes);
if FNumConsecutiveRenderPushes > 1 then
begin
// sprintf(Logger::buffer,"RenderStateManager::PushRenderStateBlock : %d consecutive pushes",m_NumConsecutiveRenderPushes);
// Logger::OutputLine();
end;
end;

{ gets the last set of states set, and restores the previous values }
procedure TDKRenderStateManager.PopRenderStateBlock;
var
Index: Cardinal;
Start, Count: Cardinal;
RS: PDKRenderState;
begin
if FRenderStateStackDepth > 0 then
begin
Dec(FRenderStateStackDepth);
Start := FRenderStateStackIndexes[FRenderStateStackDepth].Start;
Count := FRenderStateStackIndexes[FRenderStateStackDepth].Count;
for Index := Start to Start + Count - 1 do
begin
RS := @FRenderStateStack[Index];
FRenderStates[RS^.State] := RS^.Value;
FDevice.SetRenderState(RS^.State, RS^.Value);
end;
FNumConsecutiveRenderPushes := 0;
end;
end;

{ pass in a set of stage states and their desired values. The current values for these
set of states are recorded, and the new values applied }
procedure TDKRenderStateManager.PushTextureStageStateBlock(B lock: PDKTextureStageState; Count: Cardinal);
var
Index: Cardinal;
StackIndex: PDKStackIndex;
TSS: PDKTextureStageState;
begin
Assert(FTextureStageStateStackDepth < MAX_TEXTURE_STAGE_STATE_STACK_DEPTH - 1);
Index := 0;
if FTextureStageStateStackDepth > 0 then
begin
StackIndex := @FTextureStageStateStackIndexes[FTextureStageStateStackDepth - 1];
Index := StackIndex^.Start + StackIndex^.Count;
end;
Inc(FTextureStageStateStackDepth);
StackIndex := @FTextureStageStateStackIndexes[FTextureStageStateStackDepth - 1];
StackIndex^.Start := Index;
StackIndex^.Count := 0;

TSS := Block;
//build a list of the current values for each state which is to change
while Count > 0 do
begin
if TSS^.Value <> FTextureStageStates[TSS^.Stage, TSS^.State] then
begin
Assert(Index < MAX_TEXTURE_STAGE_STATE_STACK_SIZE - 1);
FTextureStageStateStack[Index].Stage := TSS^.Stage;
FTextureStageStateStack[Index].State := TSS^.State;
FTextureStageStateStack[Index].Value := FTextureStageStates[TSS^.Stage, TSS^.State];
Inc(Index);
StackIndex^.Count := Index - StackIndex^.Start;
//set and record the new value for this state
FTextureStageStates[TSS^.Stage, TSS^.State] := TSS^.Value;
FDevice.SetTextureStageState(TSS^.Stage, TSS^.State, TSS^.Value);
end
else
begin
Inc(FNumTextureStageRedundantStateCalls);
end;
Dec(Count);
Inc(FNumTextureStageStateCalls);
Inc(TSS);
end;

Inc(FNumConsecutiveTextureStagePushes);
if FNumConsecutiveTextureStagePushes > 1 then
begin
// sprintf(Logger::buffer,"RenderStateManager::PushTextureStageStateBlock : %d consecutive pushes", m_NumConsecutiveTextureStagePushes);
// Logger::OutputLine();
end;
end;

{ gets the last set of states set, and restores the previous values }
procedure TDKRenderStateManager.PopTextureStageStateBlock;
var
Index: Cardinal;
Start, Count: Cardinal;
TSS: PDKTextureStageState;
begin
if FTextureStageStateStackDepth > 0 then
begin
Dec(FTextureStageStateStackDepth);
Start := FTextureStageStateStackIndexes[FTextureStageStateStackDepth].Start;
Count := FTextureStageStateStackIndexes[FTextureStageStateStackDepth].Count;
for Index := Start to Start + Count - 1 do
begin
TSS := @FTextureStageStateStack[Index];
FTextureStageStates[TSS^.Stage, TSS^.State] := TSS^.Value;
FDevice.SetTextureStageState(TSS^.Stage, TSS^.State, TSS^.Value);
end;
FNumConsecutiveTextureStagePushes := 0;
end;
end;

procedure TDKRenderStateManager.BeginScene;
begin
FNumRenderStateCalls := 0;
FNumRenderRedundantStateCalls := 0;
FNumTextureStageStateCalls := 0;
FNumTextureStageRedundantStateCalls := 0;
FNumConsecutiveRenderPushes := 0;
FNumConsecutiveTextureStagePushes := 0;
end;

procedure TDKRenderStateManager.EndScene;
begin
// sprintf(Logger::buffer,"RenderStateManager::EndScene : Render %u/%u, TextureStage %u/%u",
// m_NumRenderRedundantStateCalls,m_NumRenderStateCal ls,m_NumTextureStageRedundantStateCalls,m_NumTextu reStageStateCalls);
// Logger::OutputLine();
end;

end.

Edit: The pascal tags do not recognize the original Pascal style comments.

technomage
28-10-2005, 08:54 AM
that is quite a nice bit of code :D I like the idea of pushing the current state and poping it once you've finished.

i might try and replicate this kind of system in opengl. :D

savage
28-10-2005, 12:07 PM
I'd be interested in seeing an OpenGL version.

technomage
28-10-2005, 04:26 PM
I would too :wink:

I'll try and make it generic enough for people to use if with only a few modifications.