Comments on: The Virtual and No-Virtual Is not better with copy past and #ifdef... <code> #ifndef __GFX_DEVICE_H__ #define __GFX_DEVICE_H__ #ifdef __PS3_H__ #include "Drivers/PS3/GfxDevice.h" #elif defined __X360_H__ #include "Drivers/X360/GfxDevice.h" #elif ... //... #endif #endif </code> <code> #ifndef __GFX_DEVICE_PS3_H__ #define __GFX_DEVICE_PS3_H__ class GfxDevice { public: //... void SetShader (ShaderType type, ShaderID shader); void SetTexture (int unit, TextureID texture); void SetGeometry (VertexBufferID vb, IndexBufferID ib); void Draw (PrimitiveType prim, int primCount); //... }; #endif </code> And PS3 Projet #include "GfxDevice.h" and GfxDevicePS3.cpp... ? Is not better with copy past and #ifdef…


#ifndef __GFX_DEVICE_H__
#define __GFX_DEVICE_H__

#ifdef __PS3_H__
#include "Drivers/PS3/GfxDevice.h"
#elif defined __X360_H__
#include "Drivers/X360/GfxDevice.h"
#elif ...
//...
#endif

#endif


#ifndef __GFX_DEVICE_PS3_H__
#define __GFX_DEVICE_PS3_H__

class GfxDevice
{
public:
//...
void SetShader (ShaderType type, ShaderID shader);
void SetTexture (int unit, TextureID texture);
void SetGeometry (VertexBufferID vb, IndexBufferID ib);
void Draw (PrimitiveType prim, int primCount);
//...
};

#endif

And PS3 Projet #include “GfxDevice.h” and GfxDevicePS3.cpp… ?

]]>
By: Will/2011/02/01/the-virtual-and-no-virtual/#comment-473 Will Wed, 02 Feb 2011 12:00:30 +0000 @Matthew: from what I can tell, the reasons for all code being read-only are not technical; it's purely to have "more security" (all code must be signed & not possible to generate/modify code at runtime). How of much of actual security that brings is a different questions, especially in the context of "int rnd() { return 4; }" stories...Some consoles/platforms do allow code modification. For example, on PS3 you can do anything you want with SPU code; on Wii you can generate/JIT code and likewise on Android. But not on Xbox 360, PS3 PPU or iOS. @Matthew: from what I can tell, the reasons for all code being read-only are not technical; it’s purely to have “more security” (all code must be signed & not possible to generate/modify code at runtime). How of much of actual security that brings is a different questions, especially in the context of “int rnd() { return 4; }” stories…Some consoles/platforms do allow code modification. For example, on PS3 you can do anything you want with SPU code; on Wii you can generate/JIT code and likewise on Android. But not on Xbox 360, PS3 PPU or iOS.

]]>
By: Matthew Steel/2011/02/01/the-virtual-and-no-virtual/#comment-471 Matthew Steel Tue, 01 Feb 2011 22:24:10 +0000 @Aras you could use explicit template instantiation to prevent exposing the implementation, and saving a minute or 2 on the build time. @Aras you could use explicit template instantiation to prevent exposing the implementation, and saving a minute or 2 on the build time.

]]>
By: davehboi/2011/02/01/the-virtual-and-no-virtual/#comment-469 davehboi Tue, 01 Feb 2011 22:07:41 +0000 @Matthew: I'm not sure I like the template approach very much (biggest problem: I really dislike exposing _all_ the implementation in the header files). If the renderer choice can be done at compile time, I'd rather just have a public interface (class GfxDevice), and multiple implementations of the same class (and each platform compiles just one implenentation).Self modifying code is not possible on some platforms. E.g. on iOS (just like with most consoles), all executable memory pages are read only - which effectively means you can't generate code or modify it at runtime. @Matthew: I’m not sure I like the template approach very much (biggest problem: I really dislike exposing _all_ the implementation in the header files). If the renderer choice can be done at compile time, I’d rather just have a public interface (class GfxDevice), and multiple implementations of the same class (and each platform compiles just one implenentation).Self modifying code is not possible on some platforms. E.g. on iOS (just like with most consoles), all executable memory pages are read only – which effectively means you can’t generate code or modify it at runtime.

]]>
By: Matthew Steel/2011/02/01/the-virtual-and-no-virtual/#comment-467 Matthew Steel Tue, 01 Feb 2011 21:05:55 +0000 Thanks for all the comments so far! @Julien: in the "approach #2" case, there would be no GfxDeviceGCM class at all. Just a GCM implementation of a (non virtual) GfxDevice interface that gets compiled into PS3 build. @Bjoern: depends (like always)... Having a low level interface is a good starting point when porting a single-platform codebase, or when you can't possibly know in what way that interface will be used. In many real world cases, a higher level approach suggested by @nickpwd is probably better. In some other situations a platform independent command buffer-like interface would perhaps be more suitable (real non-console world example that springs to mind: WebGL implementation in Chrome, it forwards all client WebGL calls into a sandboxed renderer process via a shared memory command buffer). @nickpwd: agreed, in many cases a higher level interface is much better. Right now at work (Unity) we still have something in between as GfxDevice interface. It started really low level when porting from single platform code (fastest to get up & running), then gradually over time more and more functions got merged together. Someday we'll have the single DrawList function ;) Thanks for all the comments so far!

@Julien: in the “approach #2″ case, there would be no GfxDeviceGCM class at all. Just a GCM implementation of a (non virtual) GfxDevice interface that gets compiled into PS3 build.

@Bjoern: depends (like always)… Having a low level interface is a good starting point when porting a single-platform codebase, or when you can’t possibly know in what way that interface will be used. In many real world cases, a higher level approach suggested by @nickpwd is probably better. In some other situations a platform independent command buffer-like interface would perhaps be more suitable (real non-console world example that springs to mind: WebGL implementation in Chrome, it forwards all client WebGL calls into a sandboxed renderer process via a shared memory command buffer).

@nickpwd: agreed, in many cases a higher level interface is much better. Right now at work (Unity) we still have something in between as GfxDevice interface. It started really low level when porting from single platform code (fastest to get up & running), then gradually over time more and more functions got merged together. Someday we’ll have the single DrawList function ;)

]]>
By: nickpwd/2011/02/01/the-virtual-and-no-virtual/#comment-465 nickpwd Tue, 01 Feb 2011 17:51:21 +0000 Good post. Another approach is to change your abstraction a bit. Your approach is providing a thin wrapper over a number of API calls: setting shaders, textures, and drawing individual primitives. Instead, our abstraction could be "draw a bunch of a geometry with a set of shaders and textures" like: <code><pre> struct GfxMaterial{ ShaderID vertexShader; ShaderID pixelShader; TextureID textures[TEXTURE_UNIT_COUNT]; }; struct GfxGeometry{ VertexBufferID vertexBuffer; IndexBufferID indexBuffer; PrimitiveType primType; unsigned int primCount; }; struct GfxDevice{ virtual void DrawGeoList( GfxMaterial *material, GfxGeometry *geo, unsigned int geoCount ) = 0; }; </pre></code> Now instead of making at least 5 virtual function calls per draw call (two to set shaders, at least one to set textures, one to set geo, one to draw) you make only one. One important point is that this abstraction recognizes that you are likely to draw multiple pieces of geo that share the same material.We can even take this one step further. When was the last time you only rendered one geometry/material pair? <code><pre> struct GfxDrawList{ GfxMaterial *materials; // drawCount entries. unsigned short *geoBegin; // drawCount entries, indexes in to geoList. unsigned short *geoEnd; // drawCount entries, indexes in to geoList. GfxGeometry *geoList; unsigned int drawCount; }; struct GfxDevice{ virtual void DrawList( GfxDrawList *drawList ) = 0; }; </pre></code> and DrawList looks something like: <code><pre> void GfxDeviceD3D::DrawList( GfxDrawList *drawList ){ for ( int materialIndex = 0; materialIndex < drawList->drawCount; ++materialIndex ) { D3DSetMaterial( drawList->materials + materialIndex ); // platform specific calls -- no virtuals! unsigned short geoIndex = drawList->geoBegin[materialIndex]; unsigned short endIndex = drawList->geoEnd[materialIndex]; while ( geoIndex <= endIndex ) { D3DDrawGeo( drawList->geoList + geoIndex ); // platform specific calls -- no virtuals! } } } </pre></code> Now you need only make a single virtual function call per pass. So you build draw lists for your depth prepass, shadow passes, lit pass, and so on. Then set up render state (preferably in a state block fashion), call Draw List, and move on to the next pass. Good post. Another approach is to change your abstraction a bit. Your approach is providing a thin wrapper over a number of API calls: setting shaders, textures, and drawing individual primitives. Instead, our abstraction could be “draw a bunch of a geometry with a set of shaders and textures” like:

 
  struct GfxMaterial{
 
  	ShaderID vertexShader;
 
  	ShaderID pixelShader;
 
  	TextureID textures[TEXTURE_UNIT_COUNT];
 
  };
 
  struct GfxGeometry{
 
  	VertexBufferID vertexBuffer;
 
  	IndexBufferID indexBuffer;
 
  	PrimitiveType primType;
 
  	unsigned int primCount;
 
  };
 
  struct GfxDevice{
 
  	virtual void DrawGeoList( GfxMaterial *material, GfxGeometry *geo, unsigned int geoCount ) = 0;
 
  };
 
  


Now instead of making at least 5 virtual function calls per draw call (two to set shaders, at least one to set textures, one to set geo, one to draw) you make only one. One important point is that this abstraction recognizes that you are likely to draw multiple pieces of geo that share the same material.We can even take this one step further. When was the last time you only rendered one geometry/material pair?

 
  struct GfxDrawList{
 
  	GfxMaterial *materials; // drawCount entries.
 
  	unsigned short *geoBegin; // drawCount entries, indexes in to geoList.
 
  	unsigned short *geoEnd; // drawCount entries, indexes in to geoList.
 
  	GfxGeometry *geoList;
 
  	unsigned int drawCount;
 
  };
 
  struct GfxDevice{
 
  	virtual void DrawList( GfxDrawList *drawList ) = 0;
 
  };
 
  

and DrawList looks something like:

 
  void GfxDeviceD3D::DrawList( GfxDrawList *drawList ){
 
  	for ( int materialIndex = 0; materialIndex < drawList->drawCount; ++materialIndex )
 
  	{
 
  		D3DSetMaterial( drawList->materials + materialIndex ); // platform specific calls -- no virtuals!
 
  		unsigned short geoIndex = drawList->geoBegin[materialIndex];
 
  		unsigned short endIndex = drawList->geoEnd[materialIndex];
 
  		while ( geoIndex <= endIndex )
 
  		{
 
  			D3DDrawGeo( drawList->geoList + geoIndex ); // platform specific calls -- no virtuals!
 
  		}
 
  	}
 
  }
 
  


Now you need only make a single virtual function call per pass. So you build draw lists for your depth prepass, shadow passes, lit pass, and so on. Then set up render state (preferably in a state block fashion), call Draw List, and move on to the next pass.

]]>
By: Max Burke/2011/02/01/the-virtual-and-no-virtual/#comment-463 Max Burke Tue, 01 Feb 2011 16:49:06 +0000 Great to follow example and explanations - thanks for that post! Mainly wondering now: where do you/would you draw the line to create a low-level abstraction (e.g. same interface for different platforms to create/manipulate/destroy textures) or to offer a higher-level interface, e.g. resource library and render-command queue, that hides all these platform details completely and offers different platform backends (e.g. via the dynamic lib linking you describe)? Great to follow example and explanations – thanks for that post! Mainly wondering now: where do you/would you draw the line to create a low-level abstraction (e.g. same interface for different platforms to create/manipulate/destroy textures) or to offer a higher-level interface, e.g. resource library and render-command queue, that hides all these platform details completely and offers different platform backends (e.g. via the dynamic lib linking you describe)?

]]>
By: Johan Torp/2011/02/01/the-virtual-and-no-virtual/#comment-461 Johan Torp Tue, 01 Feb 2011 12:07:17 +0000 Of course you’d need to add a dependency on this lib from all over your code

]]>
By: Julien Hamaide/2011/02/01/the-virtual-and-no-virtual/#comment-460 Julien Hamaide Tue, 01 Feb 2011 10:35:11 +0000