Forum

Reimagining Some of the Engine ...

Discuss programming topics for the various GPL'd game engine sources.

Moderator: InsideQC Admins

Reimagining Some of the Engine ...

Postby Baker » Thu Aug 09, 2012 10:14 am

Got kind of tired of making modifications to the Quake engine, mostly because getting to do anything "fun" can be too time consuming get cool results. You have to re-write everything and it kind of limits experimentation speed ...

Every operating system has its own file structure and Vista/Win 7 and beyond don't like writing to the application path. And it doesn't make sense to write intentional user stuff in the same place as potential engine temp files (i.e. I download a map or a install a mod, that isn't the same as my config ... I can re-download Travail, but I can't re-download a demo I made).

Code: Select all
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   // These are the subfolders used by the engine on Windows
   #define ENGINE_WIN32_CACHES_SUBPATH   "caches"
   #define ENGINE_WIN32_DATA_SUBPATH   "Gamedata"

   char binaryPath[MAX_OSPATH_IS_1024];
   
   // Stage 1: Finalize versioning names
   Host_SetIdentity         (System_OS_Version());

   // Stage 2: Finalize path names
   StringLCopy (binaryPath, System_GetBinaryFullPath());
   String_Edit_Remove_FileName (binaryPath);

   Host_SetDirectories (
      /* Binary path */      System_GetBinaryFullPath(),
      /* Input data path */   va ("%s/%s", binaryPath, ENGINE_WIN32_DATA_SUBPATH),
      /* Write docs path */   va ("%s/%s", System_GetAppDataPath (), host.appName),
      /* Cache write dir */   va ("%s/%s/%s", System_GetAppDataPath (), host.appName, ENGINE_WIN32_CACHES_SUBPATH)
   );

   // Stage 3: Wire it up

   // Fill in any Windows specific stuff here
   w32_plat.hInstance            = hInstance;
   w32_plat.registeredClassName      = host.appVersionString;
   w32_plat.hIcon               = LoadIcon (w32_plat.hInstance, MAKEINTRESOURCE (IDI_ICON1));   // ID1_ICON1 is defined in resource.h

   Host_Init ();      // Keep separate in case we need access to memory, messages, earlier.
   Host_Main_Loop ();   // Main loop
   Host_Shutdown ();   // Shut everything down

   return (0); // Success
}


And taking the Host operations, which really don't vary much and giving it a home ... this never belonged in sys_win.c to begin with ...

Code: Select all
void Host_Main_Loop (void)
{
   unsigned previous_time_milliseconds = System_Milliseconds (); // Note ... this will initialize time, which does not count as a subsystem at least at this time

   //   Main program loop
   while (!host.terminate)
   {
      Platform_Run_Loop_Events (); // Let system handle messages, decide to sleep, whatever

      host.time_milliseconds      = System_Milliseconds ();
      host.timeslice            = host.time_milliseconds - previous_time_milliseconds;
      previous_time_milliseconds   = host.time_milliseconds;

      Host_Frame (host.timeslice);
   }
}



And taking all the operating system specific crap, isolating it in one place ...

Code: Select all
typedef struct
{
   // Required to construct a window
   HINSTANCE   hInstance;
   HICON      hIcon;                  // Window icon
   const char   *registeredClassName;      // Set to the application version string

   // Window extra details ...
   HWND      mainwindow;               // HWND for our window
   HDC         maindc;                  // device context for our window
   HGLRC      mainRC;                  // rendering context
} platform_t;



Throw everything that can possibly be system neutral, but global, into its own "haystack".

Code: Select all
typedef struct
{
   const char   *operatingSysText;   // Text description of operating system
   const char  *appName;               // Application name
   const char   *appVersion;            // The version string
   const char   *appVersionString;         // Application version name

   // All these directories are full operating system path ...
    const char   *binaryFileName;         // (Not fullpath)
   const char   *binaryDir;               // Path to the binary (yeah, it may not be an ".exe")
    const char   *bundleDir;               // Input. <Program Files/whatever> Data that the application needs. (pak files)
    const char   *documentsDir;            // Output. <My Documents Something>User created files (save games, configs, etc.)
   const char   *cacheDir;               // Temp files, downloaded maps and such.  <App Data something> Non-user created content directory

   // Display environment ... on Windows this is the desktop for the primary monitor
   int         deviceWidth;
   int         deviceHeight;
   int         deviceBitsPerPixel;
   int         deviceRefreshRate;
   float      deviceScale;

   // Display state info ...
   fbool      cl_isFullScreen;         // Are we displaying fullscreen (therefore we probably changed the display settings
   int         cl_width;               // Client width
   int         cl_height;               // Client height
   int         cl_bitsPerPixel;         // Client bits per pixel
   int         cl_refreshRate;            // Refresh rate in Hz
   char      cl_describeVideo[64];      // Description

   fRECT      windowArea;
   fRECT      clientArea;
   int         borderAreaWidth;
   int         borderAreaHeight;
   int         mouse_center_x;            // Center of window to keep mouse centered
   int         mouse_center_y;            // Center of window to keep mouse centered

   // Messaging state ...
   fbool      isAltTabCapable;         // If we have no video window, we can't ALT-TAB
   fbool      isMinimized;
   fbool      isActiveApp;
   fbool      isVideoSuspended;
   fbool      isSoundSuspended;

   // Generate start
   fbool      startup_complete;

   // Timing
   fbool      terminate;               // If set to true a normal "Quit" has been issued.  Finishes frame.  Exits ..
   unsigned   time_milliseconds;
   unsigned   timeslice;
   

} host_t;


This function in Quake is a hopeless mess. Some MHness in this. Rather insistent on sizing at any time. Note how the bordersize is known ...

Code: Select all
LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
   // MSDSN: If an application processes this message, it should return zero to continue
   
   // check for input messages
   if (Win32_ReadInputMessages (hWnd, uMsg, wParam, lParam)) return 0;

   switch (uMsg)
   {
   case WM_ACTIVATE:      // WM_ACTIVATE: Sent to both the window being activated and the window being deactivated.
                     Platform_Focus_Change (LOWORD(wParam) != WA_INACTIVE, (fbool)HIWORD(wParam));
                     return 0;

                     
   case WM_KILLFOCUS:      // WM_KILLFOCUS: Sent to a window immediately before it loses the keyboard focus.
                     if (host.cl_isFullScreen)
                        ShowWindow (w32_plat.mainwindow, SW_SHOWMINNOACTIVE); // Minmize if fullscreen
                     return 0;

                     
   case WM_CLOSE:         // WM_CLOSE: Signal that a window/application should terminate.
                     Host_Normal_Quit ();
                     return 0;
                     
   case WM_CREATE:         // WM_CREATE: After window created, but before is visible
   case WM_DESTROY:      // WM_DESTROY: Sent when a window immediately after removed from screen
   case WM_SYSCHAR:      // WM_SYSCHAR: keep Alt-Space from happening and any ALT + letter combination
                     return 0;

   case WM_SIZE:         // WM_SIZE:   Sent to a window after its size has changed.
                     Platform_Window_Update_Info ();
                     Host_Render_Frame ();
                     return 0;


   case WM_MOVE:         // WM_MOVE: Sent after a window has been moved.
                     Platform_Window_Update_Info ();
                     return 0;
                     

   case WM_GETMINMAXINFO:   // WM_GETMINMAXINFO: Sent before size change; can be used to override default mins/maxs
                     if (host.isAltTabCapable) // If we aren't ALT-TAB capable, we cannot resize.
                     {
                        MINMAXINFO *mmi = (MINMAXINFO *) lParam;

                        mmi->ptMinTrackSize.x = 320 + host.borderAreaWidth;
                        mmi->ptMinTrackSize.y = 200 + host.borderAreaHeight;
                     }
                     return 0;


   }

   // pass all unhandled messages to DefWindowProc
   return DefWindowProc (hWnd, uMsg, wParam, lParam);

}


More stuff to some degree shaped by looking at MH's code ... note that I'm not doing host.windowArea = some RECT but host.windowArea.width = some RECT.width, etc.

This is because I'm not going to use a Windows RECT to store the client area or the Window area. Those are not really Windows-specific pieces of information, so I use an fRECT where f = Fake but the same and I can use that data structure on anything. I also avoid qboolean data type because it is {false, true} and that is a C++ datatype. So I use {False, True}, which is annoying because I prefer "true and false" ... but "True and False" don't conflict with C++ and I do want to potentially be able to utilize C++ code if the situation fits, and if I use "true and false" ... a mess begins being unable to share structs.

Code: Select all
// Refresh rects and mouse center
// Called at window resize/move and window construction.

void Platform_Window_Update_Info (void)
{
   WINDOWINFO windowinfo;
   RECT twindowArea;
   
   windowinfo.cbSize = sizeof (WINDOWINFO);

   GetWindowRect (w32_plat.mainwindow, &twindowArea);
   GetWindowInfo (w32_plat.mainwindow, &windowinfo);

   host.windowArea.top      = twindowArea.top;
   host.windowArea.bottom   = twindowArea.bottom;
   host.windowArea.left   = twindowArea.left;
   host.windowArea.right   = twindowArea.right;


   host.clientArea.top      = windowinfo.rcClient.top;
   host.clientArea.bottom   = windowinfo.rcClient.bottom;
   host.clientArea.left   = windowinfo.rcClient.left;
   host.clientArea.right   = windowinfo.rcClient.right;

   host.mouse_center_x      = host.clientArea.left + (RECT_WIDTH(host.clientArea) >> 1);
   host.mouse_center_y      = host.clientArea.top + (RECT_HEIGHT(host.clientArea) >> 1);
   host.borderAreaWidth   = RECT_WIDTH(host.windowArea) - RECT_WIDTH(host.clientArea);
   host.borderAreaHeight   = RECT_HEIGHT(host.windowArea) - RECT_HEIGHT(host.clientArea);

   // Fill in the description

   host.cl_width         = windowinfo.rcClient.right - windowinfo.rcClient.left;
   host.cl_height         = windowinfo.rcClient.bottom - windowinfo.rcClient.top;

   SNPrintf (host.cl_describeVideo, sizeof(host.cl_describeVideo), "%i x %i %i bpp rate = %i Hz (%s)", host.cl_width, host.cl_height, host.cl_bitsPerPixel, host.cl_refreshRate, host.cl_isFullScreen ? "fullscreen": "windowed");

}


The beginnings of an "automatic shutdown" system ... the code works as-is. To hell with Sys_Error and Host_Error. There shouldn't be 2 of them. There should be Host_Error and it should just shutdown whatever is actually initialized.

Code: Select all
typedef   fbool (*func_begin) (void);
typedef   void (*func_end) (void);

typedef struct
{
   func_begin   Startup;
   func_end   Shutdown;
   fbool      initialized;
} subsystem_t;

subsystem_t subsystems [] =
{
   { Platform_Init,         Platform_Shutdown,         False   },   // Gets desktop size
   { Platform_Window_Create,   NULL,                  False   },   // Sets up video
   { Renderer_OpenGL_Init,      Renderer_OpenGL_Shutdown,   False   },   // Initializes OpenGL
};

int num_sub_systems = sizeof (subsystems) / sizeof(subsystems[0]);

// Initialize sub-systems
void Host_Init (void)
{
   int i;

   // Initialize each sub system
   for (i = 0; i < num_sub_systems; i ++)
   {
      subsystems[i].initialized = subsystems[i].Startup ();
//      if (subsystems[i].initialized)
//         Printf something               

   }

   host.startup_complete = True;

}

// Shutdown sub-systems
void Host_Shutdown (void)
{
   // Last in, first out ...   
   int i, j;
   
   for (j = 0; j < num_sub_systems; j ++)
   {
      i = (num_sub_systems - j) - 1;  // ie 10 - 0 - 1 = starts at 9
      if (!subsystems[i].initialized || subsystems[i].Shutdown == NULL)
         continue;

      subsystems[i].Shutdown ();
//         Printf something
   }
}


What I really want to do is rapidly put this all together and then start doing geometry, collision, lighting, particle and prediction/networking experiments. It is too hard to do most of that with Quake because the number of changes required grows quickly out of control.

End blah ...
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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Reimagining Some of the Engine ...

Postby leileilol » Fri Aug 10, 2012 2:08 am

Making WinQuake a bit more "Windows 95" like could be fun.

- Resizable display context (on GL this no doubt would be evil for reinitializing, but software should be fine and stretchable to anything above 320x200)
- A menu bar with options, video modes (ala Unreal and Jazz Jackrabbit 2)
- a custom title bar (ala Jazz Jackrbabit 2 and some other games)
i should not be here
leileilol
 
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Re: Reimagining Some of the Engine ...

Postby Baker » Fri Aug 10, 2012 3:55 am

Me personally, I'm trying to imagine a world where maintaining multi-platform projects -- in particular Quake (although I'm kinda focused on the GL side) isn't such a son of a bitch [trying to keep in mind what Spike said about Q3 system agnostic input queuing].

But yeah, I'd kill for one of your above in software rendering. Fruitz of Dojo Quake actually does that on a Mac (running 320x200 software mode in a larger sized window like 640 x 400 or 960 x 600, etc.)

/Wishes the software renderer code wasn't such obtuse spaghetti as software rendering is completely multiplatform neutral.
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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Reimagining Some of the Engine ...

Postby r00k » Fri Aug 10, 2012 4:03 am

Once you start multi-platform support, this is a never ending task * 3.
r00k
 
Posts: 1110
Joined: Sat Nov 13, 2004 10:39 pm

Re: Reimagining Some of the Engine ...

Postby Baker » Fri Aug 10, 2012 4:27 am

r00k wrote:Once you start multi-platform support, this is a never ending task * 3.

There is some truth in 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 ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am


Return to Engine Programming

Who is online

Users browsing this forum: No registered users and 1 guest