Switchable Renderer Notes

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Switchable Renderer Notes

Post by Baker »

I'm a big fan of MH's Direct3D 8.1 wrapper for a number of reasons. First is the quality of how it was written and how easy it is to plug into an engine.

I'm going to use this thread for documenting stuff and it might even result in a mini-project of making a FitzQuake 0.85 combo renderer tomorrow morning.

I've spend a lot of time over the last year using MH's wrapper hands-on and after thinking about FTEQW's switchable renderer and thinking about multiplatform stuffs (DarkPlaces and AGL on OS X come to mind).

Here are some rough thoughts/notes:
Notes wrote:1. I have the initial version of a switchable renderer version of my engine done. It was kind of anti-climatic starting it up.
2. My approach was to link to both the opengl library and use MH's wrapper natively. I notice that FTEQW (and DarkPlaces?) appears to dynamically link with opengl32.dll, which is technically a superior way. A weakness of the way I did it, you get dependencies of both OpenGL and Direct3D 8.1 which isn't actually "better" as you've increased the chances of the engine encountering a machine that the engine won't run on.
3. To fine-tune my stuff, I'm going to have to build OpenGL only and Direct3D only #ifdefs to ensure I have everything properly separated.
In an idea world, MH's wrapper would be turned into a .dll. This would help both the engine code size (not really an issue, my engine grew from 760KB to 934KB with a combo renderer build -- not a big deal), but more to allow dynamic linking to each.

Render restarting stuff: I haven't implemented this yet, but the gist of the idea is that you'll have to check extensions and check the hardware stuffs (max TMUs, etc.). MH's wrapper doesn't support vsync inherently, but MH gave me some instructions to make it support vsync if you know in advance (like a command line param) and with renderer restart, it shouldn't be a problem.

Other notes:

I like the idea of an engine structure that keeps track of everything, but also want modular design so that components don't require headers they shouldn't require and are transportable from project to project. And although this isn't exactly rocket science, I shifted the Renderer initialization procedure to return a pointer with the idea that eventually all the initialization functions in Host_Init will return pointers (where it is a good fit).

Code: Select all

engine.renderer = Renderer_Init (RENDERER_OPENGL);

Code: Select all

// renderer.h -- Renderer neutral modular component


#ifndef RENDERER_H
#define RENDERER_H

#define RENDERER_OPENGL   1
#define RENDERER_DIRECT3D 2

typedef struct
{
	char	RendererText[20];
	int		graphics_api;			 //1 = OpenGL, 2 = Direct3D, 0 = Uninitialized
	qbool	initialized;
} renderer_def_t;


renderer_def_t myRenderer;		

#endif

Code: Select all

// renderer.c -- Renderer neutral modular component

#include "quakedef.h"
#include "gl_local.h"	// Baker: Needed

void Renderer_D3D (void)
{
	eglAlphaFunc            = d3dmh_glAlphaFunc;
	eglBegin                = d3dmh_glBegin;
	eglBindTexture          = d3dmh_glBindTexture;
	eglBlendFunc            = d3dmh_glBlendFunc;
	eglClear                = d3dmh_glClear;
	eglClearColor           = d3dmh_glClearColor;
	eglClearStencil         = d3dmh_glClearStencil;
	eglColor3f              = d3dmh_glColor3f;
	eglColor3fv             = d3dmh_glColor3fv;
	eglColor3ubv            = d3dmh_glColor3ubv;
	eglColor4f              = d3dmh_glColor4f;
	eglColor4fv             = d3dmh_glColor4fv;
	eglColor4ub             = d3dmh_glColor4ub;
	eglColor4ubv            = d3dmh_glColor4ubv;
	eglColorMask            = d3dmh_glColorMask;
	eglCullFace             = d3dmh_glCullFace;
	eglDeleteTextures       = d3dmh_glDeleteTextures;
	eglDepthFunc            = d3dmh_glDepthFunc;
	eglDepthMask            = d3dmh_glDepthMask;
	eglDepthRange           = d3dmh_glDepthRange;
	eglDisable              = d3dmh_glDisable;
	eglDrawBuffer           = d3dmh_glDrawBuffer;
	eglEnable               = d3dmh_glEnable;
	eglEnd                  = d3dmh_glEnd;
	eglFinish               = d3dmh_glFinish;
	eglFogf                 = d3dmh_glFogf;
	eglFogfv                = d3dmh_glFogfv;
	eglFogi                 = d3dmh_glFogi;
	eglFogiv                = d3dmh_glFogiv;
	eglFrontFace            = d3dmh_glFrontFace;
	eglFrustum              = d3dmh_glFrustum;
	eglGenTextures          = d3dmh_glGenTextures;
	eglGetFloatv            = d3dmh_glGetFloatv;
	eglGetIntegerv          = d3dmh_glGetIntegerv;
	eglGetString            = d3dmh_glGetString;
	eglGetTexImage          = d3dmh_glGetTexImage;
	eglGetTexParameterfv    = d3dmh_glGetTexParameterfv;
	eglHint                 = d3dmh_glHint;
	eglLoadIdentity         = d3dmh_glLoadIdentity;
	eglLoadMatrixf          = d3dmh_glLoadMatrixf;
	eglMatrixMode           = d3dmh_glMatrixMode;
	eglMultMatrixf          = d3dmh_glMultMatrixf;
	eglNormal3f             = d3dmh_glNormal3f;
	eglOrtho                = d3dmh_glOrtho;
	eglPolygonMode          = d3dmh_glPolygonMode;
	eglPolygonOffset        = d3dmh_glPolygonOffset;
	eglPopMatrix            = d3dmh_glPopMatrix;
	eglPushMatrix           = d3dmh_glPushMatrix;
	eglReadBuffer           = d3dmh_glReadBuffer;
	eglReadPixels           = d3dmh_glReadPixels;
	eglRotatef              = d3dmh_glRotatef;
	eglScalef               = d3dmh_glScalef;
	eglScissor              = d3dmh_glScissor;
	eglShadeModel           = d3dmh_glShadeModel;
	eglStencilFunc          = d3dmh_glStencilFunc;
	eglStencilOp            = d3dmh_glStencilOp;
	eglTexCoord2f           = d3dmh_glTexCoord2f;
	eglTexCoord2fv          = d3dmh_glTexCoord2fv;
	eglTexEnvf              = d3dmh_glTexEnvf;
	eglTexEnvi              = d3dmh_glTexEnvi;
	eglTexImage2D           = d3dmh_glTexImage2D;
	eglTexParameterf        = d3dmh_glTexParameterf;
	eglTexParameteri        = d3dmh_glTexParameteri;
	eglTexSubImage2D        = d3dmh_glTexSubImage2D;
	eglTranslatef           = d3dmh_glTranslatef;
	eglVertex2f             = d3dmh_glVertex2f;
	eglVertex2fv            = d3dmh_glVertex2fv;
	eglVertex3f             = d3dmh_glVertex3f;
	eglVertex3fv            = d3dmh_glVertex3fv;
	eglViewport             = d3dmh_glViewport;

	
	ewglCreateContext       = d3dmh_wglCreateContext;
	ewglDeleteContext       = d3dmh_wglDeleteContext;
	ewglGetCurrentContext   = d3dmh_wglGetCurrentContext;
	ewglGetCurrentDC        = d3dmh_wglGetCurrentDC;
	ewglMakeCurrent         = d3dmh_wglMakeCurrent;
	ewglGetProcAddress		= d3dmh_wglGetProcAddress;

	eSetPixelFormat         = d3dmh_SetPixelFormat;

	eChangeDisplaySettings  = ChangeDisplaySettings_FakeGL;

	myRenderer.graphics_api	= RENDERER_DIRECT3D;
	strcpy (myRenderer.RendererText, "DX8");
}

void Renderer_OpenGL (void)
{
	eglAlphaFunc            = glAlphaFunc;
	eglBegin                = glBegin;
	eglBindTexture          = glBindTexture;
	eglBlendFunc            = glBlendFunc;
	eglClear                = glClear;
	eglClearColor           = glClearColor;
	eglClearStencil         = glClearStencil;
	eglColor3f              = glColor3f;
	eglColor3fv             = glColor3fv;
	eglColor3ubv            = glColor3ubv;
	eglColor4f              = glColor4f;
	eglColor4fv             = glColor4fv;
	eglColor4ub             = glColor4ub;
	eglColor4ubv            = glColor4ubv;
	eglColorMask            = glColorMask;
	eglCullFace             = glCullFace;
	eglDeleteTextures       = glDeleteTextures;
	eglDepthFunc            = glDepthFunc;
	eglDepthMask            = glDepthMask;
	eglDepthRange           = glDepthRange;
	eglDisable              = glDisable;
	eglDrawBuffer           = glDrawBuffer;
	eglEnable               = glEnable;
	eglEnd                  = glEnd;
	eglFinish               = glFinish;
	eglFogf                 = glFogf;
	eglFogfv                = glFogfv;
	eglFogi                 = glFogi;
	eglFogiv                = glFogiv;
	eglFrontFace            = glFrontFace;
	eglFrustum              = glFrustum;
	eglGenTextures          = glGenTextures;
	eglGetFloatv            = glGetFloatv;
	eglGetIntegerv          = glGetIntegerv;
	eglGetString            = glGetString;
	eglGetTexImage          = glGetTexImage;
	eglGetTexParameterfv    = glGetTexParameterfv;
	eglHint                 = glHint;
	eglLoadIdentity         = glLoadIdentity;
	eglLoadMatrixf          = glLoadMatrixf;
	eglMatrixMode           = glMatrixMode;
	eglMultMatrixf          = glMultMatrixf;
	eglNormal3f             = glNormal3f;
	eglOrtho                = glOrtho;
	eglPolygonMode          = glPolygonMode;
	eglPolygonOffset        = glPolygonOffset;
	eglPopMatrix            = glPopMatrix;
	eglPushMatrix           = glPushMatrix;
	eglReadBuffer           = glReadBuffer;
	eglReadPixels           = glReadPixels;
	eglRotatef              = glRotatef;
	eglScalef               = glScalef;
	eglScissor              = glScissor;
	eglShadeModel           = glShadeModel;
	eglStencilFunc          = glStencilFunc;
	eglStencilOp            = glStencilOp;
	eglTexCoord2f           = glTexCoord2f;
	eglTexCoord2fv          = glTexCoord2fv;
	eglTexEnvf              = glTexEnvf;
	eglTexEnvi              = glTexEnvi;
	eglTexImage2D           = glTexImage2D;
	eglTexParameterf        = glTexParameterf;
	eglTexParameteri        = glTexParameteri;
	eglTexSubImage2D        = glTexSubImage2D;
	eglTranslatef           = glTranslatef;
	eglVertex2f             = glVertex2f;
	eglVertex2fv            = glVertex2fv;
	eglVertex3f             = glVertex3f;
	eglVertex3fv            = glVertex3fv;
	eglViewport             = glViewport;
	
	ewglCreateContext       = wglCreateContext;
	ewglDeleteContext       = wglDeleteContext;
	ewglGetCurrentContext   = wglGetCurrentContext;
	ewglGetCurrentDC        = wglGetCurrentDC;
	ewglMakeCurrent         = wglMakeCurrent;
	ewglGetProcAddress		= wglGetProcAddress;

	eSetPixelFormat         = SetPixelFormat;

	eChangeDisplaySettings  = ChangeDisplaySettings;

	myRenderer.graphics_api	= RENDERER_OPENGL;
	strcpy (myRenderer.RendererText, "GL");
}

renderer_def_t *Renderer_Init (int RendererType)
{
	if (RendererType == RENDERER_OPENGL)
		Renderer_OpenGL ();
	else
		Renderer_D3D ();

	myRenderer.initialized  = true;
	return &myRenderer;

}

Code: Select all

...
void (APIENTRY *eglPolygonMode) (GLenum face, GLenum mode);
void (APIENTRY *eglPolygonOffset) (GLfloat factor, GLfloat units);
void (APIENTRY *eglPolygonStipple) (const GLubyte *mask);
void (APIENTRY *eglPopAttrib) (void);
void (APIENTRY *eglPopClientAttrib) (void);
void (APIENTRY *eglPopMatrix) (void);
...
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Stroggos
Posts: 50
Joined: Tue Apr 14, 2009 11:40 am
Location: Australia

Post by Stroggos »

This all looks very neat and I can't wait to see the results form this test. Good luck! :D
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Part of the problem with vsync is that OpenGL and D3D handle it differently. OpenGL doesn't care, it takes the current setting you have on your 3D card, and you can optionally enable or disable it via extensions (and sometimes your driver control panel either overrides it entirely, or may even be the only way to set it). D3D not only cares, but you must specify whether vsync is to be enabled or disabled during startup. The two approaches are obviously incompatible but at the same time a default behaviour must be chosen.

Changing vsync in D3D is just a matter of changing your present parameters and resetting the device, There are also flags available for your Present call that can fine tune the behaviour a little more, but my experience is that not all drivers honour them. D3D10 and 11 are a lot more sensible in this regard (vsync is just a runtime flag rather than a startup time property).

The only reason to dynamically load all OpenGL functions is if you want to support different OpenGL DLLs. Q2 and Q3A did it for no other reason than for 3DFX support (so that the 3DFX DLL could have a different name to the main DLL - this is verified by a comment in the code). Nothing technically superior about it at all. Beyond 3DFX support it just complicates things and causes more problems than it solves.

Call logging might look on the surface like another reason for it, but these days if you wanted call logging you'd just use GLIntercept.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Stroggos wrote:This all looks very neat and I can't wait to see the results form this test. Good luck! :D
MH did such an incredible job with the wrapper, you really won't know which one you are running.

Anyways ...

Download: FitzQuake 0.85 with OpenGL and Direct3D 8.1 Wrapper Combined - 4 MB including exe and source code and dependencies MSVC6 project

The dual renderer FitzQuake .exe file is only 676 KB, so it really didn't grow too much.

[If I weren't tired, I would have invested another hour and a half and made it an option in the menu.]
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Re: Switchable Renderer Notes

Post by mh »

Baker wrote:In an idea world, MH's wrapper would be turned into a .dll.
Totally agreed, but executable size is a completely bogus reason for this optimization. If code is never used it never gets swapped in, so it doesn't matter if the executable is 500k or 256mb; only the code that gets used gets swapped in. This is all part of the false bloatware myth which is rooted in a certain mindset of the 70s and 80s and based on the false assumption that the same rules still apply today. True bloatware is still a bad thing of course, but the entire concept has been hijacked by those of a certain persuasion and misapplied everywhere.

In any event, the code that you write is only a tiny part of your total program, and if you're coding to hardware you're operating under a completely different set of rules to if you're doing pure software. Learning how that hardware works and taking advantage of it properly is the only way to go, even if that means you end up with twice as much code. Otherwise you might have a small (and therefore "beautiful") program that might run at a fraction of it's potential performance.

Rant over. ;)
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Switchable Renderer Notes

Post by Baker »

mh wrote:Totally agreed, but executable size is a completely bogus reason for this optimization. If code is never used it never gets swapped in, so it doesn't matter if the executable is 500k or 256mb; only the code that gets used gets swapped in. This is all part of the false bloatware myth which is rooted in a certain mindset of the 70s and 80s and based on the false assumption that the same rules still apply today. True bloatware is still a bad thing of course, but the entire concept has been hijacked by those of a certain persuasion and misapplied everywhere.
Filesizes and memory aren't everything and the sizes here don't matter. The .dll thing was bit more thinking of the invalid opengl32.dll that causes Quake to not run. With dynamic linking, you could actually detect it in advance by filesize to avoid the error (sure crc or md5 would be more "precise", but there is only one of that file in existence).

I was also thinking of file dependencies. Someone whose OpenGL drivers are bad, static linking might cause the Quake executable to error out at start up.

I kind of have a reliability point-of-view with all the Quake "errors" I've seen.

Going to extremes with memory management and so forth is a waste of effort. I included the filesize of the exe and the growth since I was curious about it myself.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

The exe crashes on my windows7 when yes to directX, even in compatibility mode...
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Re: Switchable Renderer Notes

Post by Spike »

Baker wrote:Filesizes and memory aren't everything and the sizes here don't matter. The .dll thing was bit more thinking of the invalid opengl32.dll that causes Quake to not run. With dynamic linking, you could actually detect it in advance by filesize to avoid the error (sure crc or md5 would be more "precise", but there is only one of that file in existence).
True enough, but crc/md5s are probably the wrong way to do it.
There's a SetErrorMode winapi function. If used properly, you can get it to mute the errors resulting from bad imports like the glide minidriver. Use that, try to load opengl from the quake dir, if it fails, try to load it from the windows dir, if that fails too then they're using win95a and you should use d3d. The user won't see any errors, it 'just works'.
With dynamic loading you can indeed completely ignore the fact that they have the glide minidriver without glide.
Its a worthwhile feature of any Quake(1) engine, less so with quake2, yes, but for quake, ohboy.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

r00k wrote:The exe crashes on my windows7 when yes to directX, even in compatibility mode...
Wonderful. I'll have access to a 64-bit Windows 7 machine that I can do testing on Wednesday.

I'll place a 20% chance that it is somehow the video mode initialization code. I have had a crash on Windows 7 x64 with at least 2 engines, but never had MSVC on it to check and see what was up. I would bet this is related, but maybe not.

@Spike ... interesting .... will have to play with that
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

the jit in win 7 64 is on steroids :P i swear i have 50% more crashes on 64 than i had on x86.

also it seems to be rather unforgiving i once made a typo so my realm engine crashed but even if i revert it to the old non crashing code it still triggers a crash :lol:
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I'd debug it myself but the source as released doesn't compile. Couldn't be bothered. :evil:

Just one note though: MSVC6? Really? You know the way Tenebrae crashes on Windows 7 64 bit? And the way I just recompiled it with MSVC 2008 and it stopped crashing? Betcha.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:I'd debug it myself but the source as released doesn't compile.
It should have. But then again I was tired when I uploaded so maybe I only thought I dotted all the "I"'s.
Just one note though: MSVC6? Really?
I used MSVC6 for this as a minimal set of changes to the DirectFitz project to support this and DirectFitz used MSVC6.

Either way, I'll rework the project to use Visual Studio 2008 and then reupload.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply