/******************************Module*Header*******************************\ * Module Name: state.cxx * * STATE * * Copyright (c) 1995 Microsoft Corporation * \**************************************************************************/ #include #include #include #include #include #include #include #include #include "sspipes.h" #include "dialog.h" #include "state.h" #include "pipe.h" #include "npipe.h" #include "fpipe.h" #include "eval.h" // default texture resource(s) #define DEF_TEX_COUNT 1 TEX_RES gTexRes[DEF_TEX_COUNT] = { { TEX_BMP, IDB_DEFTEX } }; static void InitTexParams(); /******************************Public*Routine******************************\ * STATE constructor * * - global state init * - translates variables set from the dialog boxes * \**************************************************************************/ //mf: since pass bXXX params why not do same with ulSurfStyle, fTesselFact, // ulTexQual STATE::STATE( BOOL bFlexMode, BOOL bMultiPipes ) { // various state values resetStatus = RESET_STARTUP_BIT; // Put initial hglrc in drawThreads[0] // This RC is also used for dlists and texture objects that are shared // by other RC's shareRC = wglGetCurrentContext(); drawThreads[0].SetRCDC( shareRC, wglGetCurrentDC() ); bTexture = FALSE; if( ulSurfStyle == SURFSTYLE_TEX ) { if( LoadTextureFiles( gTexFile, gnTextures, &gTexRes[0] ) ) bTexture = TRUE; } else if( ulSurfStyle == SURFSTYLE_WIREFRAME ) { glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); } // Initialize GL state for the initial RC (sets texture state, so // (must come after LoadTextureFiles()) GLInit(); // set 'reference' radius value radius = 1.0f; // convert tesselation from fTesselFact(0.0-2.0) to tessLevel(0-MAX_TESS) int tessLevel = (int) (fTesselFact * (MAX_TESS+1) / 2.0001f); nSlices = (tessLevel+2) * 4; // Allocate basic NODE_ARRAY // NODE_ARRAY size is determined in Reshape (based on window size) nodes = new NODE_ARRAY; // Set drawing mode, and initialize accordingly. For now, either all normal // or all flex pipes are drawn, but they could be combined later. // Can assume here that if there's any possibility that normal pipes // will be drawn, NORMAL_STATE will be initialized so that dlists are // built // Again, since have either NORMAL or FLEX, set maxPipesPerFrame, // maxDrawThreads if( bMultiPipes ) maxDrawThreads = MAX_DRAW_THREADS; else maxDrawThreads = 1; nDrawThreads = 0; // no active threads yet nPipesDrawn = 0; // maxPipesPerFrame is set in Reset() if( bFlexMode ) { drawMode = DRAW_FLEX; pFState = new FLEX_STATE( this ); pNState = NULL; } else { drawMode = DRAW_NORMAL; pNState = new NORMAL_STATE( this ); pFState = NULL; } // initialize materials if( bTexture ) ss_InitTexMaterials(); else ss_InitTeaMaterials(); // default draw scheme drawScheme = FRAME_SCHEME_RANDOM; } /******************************Public*Routine******************************\ * STATE destructor * \**************************************************************************/ STATE::~STATE( ) { if( pNState ) delete pNState; if( pFState ) delete pFState; if( nodes ) delete nodes; if( bTexture ) { for( int i = 0; i < nTextures; i ++ ) { ss_DeleteTexture( &texture[i] ); } } // Delete any RC's - should be done by ~THREAD, but since common lib // deletes shareRC, have to do it here DRAW_THREAD *pdt = &drawThreads[0]; for( int i = 0; i < MAX_DRAW_THREADS; i ++, pdt++ ) { if( pdt->hglrc && (pdt->hglrc != shareRC) ) { wglDeleteContext( pdt->hglrc ); } } } /******************************Public*Routine******************************\ * CalcTexRepFactors * \**************************************************************************/ void STATE::CalcTexRepFactors() { ISIZE winSize; POINT2D texFact; ss_GetScreenSize( &winSize ); // Figure out repetition factor of texture, based on bitmap size and // screen size. // // We arbitrarily decide to repeat textures that are smaller than // 1/8th of screen width or height. for( int i = 0; i < nTextures; i++ ) { texRep[i].x = texRep[i].y = 1; if( (texFact.x = winSize.width / texture[i].width / 8.0f) >= 1.0f) texRep[i].x = (int) (texFact.x+0.5f); if( (texFact.y = winSize.height / texture[i].height / 8.0f) >= 1.0f) texRep[i].y = (int) (texFact.y+0.5f); } // ! If display list based normal pipes, texture repetition is embedded // in the dlists and can't be changed. So use the smallest rep factors. // mf: Should change this so smaller textures are replicated close to // the largest texture, then same rep factor will work well for all if( pNState ) { //put smallest rep factors in texRep[0]; (mf:this is ok for now, as // flex pipes and normal pipes don't coexist) for( i = 1; i < nTextures; i++ ) { if( texRep[i].x < texRep[0].x ) texRep[0].x = texRep[i].x; if( texRep[i].y < texRep[0].y ) texRep[0].y = texRep[i].y; } } } /******************************Public*Routine******************************\ * LoadTextureFiles * * - Load user texture files. If texturing on but no user textures, or * problems loading them, load default texture resource * mf: later, may want to have > 1 texture resource * \**************************************************************************/ BOOL STATE::LoadTextureFiles( TEXFILE *pTexFile, int nTexFiles, TEX_RES *pTexRes ) { // Set pixel store state glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Try to load the bmp or rgb file // i counts successfully loaded textures for( int i = 0; nTexFiles; nTexFiles-- ) { if( ss_LoadTextureFile( &pTexFile[i], &texture[i] ) ) { // If texture object extension, set tex params here for each object if( ss_TextureObjectsEnabled() ) InitTexParams(); i++; // count another valid texture } } // set number of valid textures in state nTextures = i; if( nTextures == 0 ) { // No user textures, or none loaded successfully // Load default resource texture(s) nTextures = DEF_TEX_COUNT; for( i = 0; i < nTextures; i++, pTexRes++ ) { if( !ss_LoadTextureResource( pTexRes, &texture[i] ) ) { // shouldn't happen return FALSE; } } } CalcTexRepFactors(); return TRUE; } /******************************Public*Routine******************************\ * GLInit * * - Sets up GL state * - Called once for every context (rc) * \**************************************************************************/ void STATE::GLInit() { static float ambient[] = {0.1f, 0.1f, 0.1f, 1.0f}; static float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; static float position[] = {90.0f, 90.0f, 150.0f, 0.0f}; static float lmodel_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; static float lmodel_ambientTex[] = {0.6f, 0.6f, 0.6f, 0.0f}; static float back_mat_diffuse[] = {0.0f, 0.0f, 1.0f}; glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glFrontFace(GL_CCW); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glEnable( GL_AUTO_NORMAL ); // needed for GL_MAP2_VERTEX (tea) if( bTexture ) glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambientTex); else glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); glLightfv(GL_LIGHT0, GL_POSITION, position); glEnable(GL_LIGHT0); glEnable(GL_LIGHTING); #if 1 glCullFace(GL_BACK); glEnable(GL_CULL_FACE); #else // debug // back material for debugging glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE ); #endif // Set texture modes if( bTexture ) { glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); InitTexParams(); } } /**************************************************************************\ * InitTexParams * * Set texture parameters, globally, or per object if texture object extension * \**************************************************************************/ static void InitTexParams() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); switch( ulTexQuality ) { case TEXQUAL_HIGH: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case TEXQUAL_DEFAULT: default: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; } } /**************************************************************************\ * Repaint * * This is called when a WM_PAINT msg has been sent to the window. The paint * will overwrite the frame buffer, screwing up the scene if pipes is in single * buffer mode. We set resetStatus accordingly to clear things up on next * draw. * \**************************************************************************/ void STATE::Repaint( LPRECT pRect, void *data) { resetStatus |= RESET_REPAINT_BIT; } /**************************************************************************\ * Reshape * - called on resize, expose * - always called on app startup * - set new window size for VIEW object, and set resetStatus for validation * at draw time * \**************************************************************************/ void STATE::Reshape(int width, int height, void *data) { if( view.SetWinSize( width, height ) ) resetStatus |= RESET_RESIZE_BIT; } /**************************************************************************\ * ResetView * * Called on FrameReset resulting from change in viewing paramters (e.g. from * a Resize event). \**************************************************************************/ void STATE::ResetView() { IPOINT3D numNodes; // Have VIEW calculate the node array size based on view params view.CalcNodeArraySize( &numNodes ); // Resize the node array nodes->Resize( &numNodes ); // Set GL viewing parameters for each active RC DRAW_THREAD *pThread = drawThreads; for( int i = 0; i < MAX_DRAW_THREADS; i ++, pThread++ ) { if( pThread->HasRC() ) { pThread->MakeRCCurrent(); view.SetGLView(); } } } /**************************************************************************\ * FrameReset * * Start a new frame of pipes * * The resetStatus parameter indicates what triggered the Reset. * \**************************************************************************/ static int PickRandomTexture( int i, int nTextures ); void STATE::FrameReset() { int i; float xRot, zRot; PIPE *pNewPipe; #ifdef DO_TIMING Timer( TIMER_STOP ); #endif SS_DBGINFO( "Pipes STATE::FrameReset:\n" ); // Kill off any active pipes ! (so they can shut down ok) DRAW_THREAD *pThread = drawThreads; for( i = 0; i < nDrawThreads; i ++, pThread++ ) { pThread->KillPipe(); } nDrawThreads = 0; // Clear the screen Clear(); // Check for window resize status if( resetStatus & RESET_RESIZE_BIT ) { ResetView(); } // Reset the node states to empty nodes->Reset(); // Call any pipe-specific state resets, and get any recommended // pipesPerFrame counts if( pNState ) { pNState->Reset(); } if( pFState ) { pFState->Reset(); //mf: maybe should figure out min spherical view dist xRot = ss_fRand(-5.0f, 5.0f); zRot = ss_fRand(-5.0f, 5.0f); } maxPipesPerFrame = CalcMaxPipesPerFrame(); // Set new number of drawing threads if( maxDrawThreads > 1 ) { // Set maximum # of pipes per frame maxPipesPerFrame = (int) (maxPipesPerFrame * 1.5); // Set # of draw threads nDrawThreads = SS_MIN( maxPipesPerFrame, ss_iRand2( 2, maxDrawThreads ) ); // Set chase mode if applicable, every now and then BOOL bUseChase = pNState || (pFState && pFState->OKToUseChase()); if( bUseChase && (!ss_iRand(5)) ) { drawScheme = FRAME_SCHEME_CHASE; } } else { nDrawThreads = 1; } nPipesDrawn = 0; // for now, either all NORMAL or all FLEX for each frame pThread = drawThreads; for( i = 0; i < nDrawThreads; i ++, pThread++ ) { // Create hglrc if necessary, and init it if( !pThread->HasRC() ) { HDC hdc = wglGetCurrentDC(); pThread->SetRCDC( wglCreateContext( hdc ), hdc ); // also need to init each RC pThread->MakeRCCurrent(); #if 0 //mf: should get this working wglCopyContext( drawThreads[0].GetRC(), pThread->GetRC(), 0xffff ); #endif // Do GL Init for this new RC GLInit(); // Set viewing params view.SetGLView(); // Give this rc access to any dlists wglShareLists( shareRC, pThread->GetRC() ); } else pThread->MakeRCCurrent(); // Set up the modeling view glLoadIdentity(); glTranslatef(0.0f, 0.0f, view.zTrans); // Rotate Scene glRotatef( view.yRot, 0.0f, 1.0f, 0.0f ); // create approppriate pipe for this thread slot switch( drawMode ) { case DRAW_NORMAL: pNewPipe = (PIPE *) new NORMAL_PIPE(this); break; case DRAW_FLEX: // There are several kinds of FLEX pipes - have FLEX_STATE // decide which one to create pNewPipe = pFState->NewPipe( this ); // rotate a bit around x and z as well // mf: ! If combining NORMAL and FLEX, same rotations must be // applied to both glRotatef( xRot, 1.0f, 0.0f, 0.0f ); glRotatef( zRot, 0.0f, 0.0f, 1.0f ); break; } pThread->SetPipe( pNewPipe ); if( drawScheme == FRAME_SCHEME_CHASE ) { if( i == 0 ) { // this will be the lead pipe pLeadPipe = pNewPipe; pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED ); } else { pNewPipe->SetChooseDirectionMethod( CHOOSE_DIR_CHASE ); } } // If texturing, pick a random texture for this thread if( bTexture ) { int index = PickRandomTexture( i, nTextures ); pThread->SetTexture( &texture[index] ); // Flex pipes need to be informed of the texture, so they // can dynamically calculate various texture params if( pFState ) ((FLEX_PIPE *) pNewPipe)->SetTexParams( &texture[index], &texRep[index] ); } // Launch the pipe (assumed: always more nodes than pipes starting, so // StartPipe cannot fail) // ! All pipe setup needs to be done before we call StartPipe, as this // is where the pipe starts drawing pThread->StartPipe(); // Kind of klugey, but if in chase mode, I set chooseStartPos here, // since first startPos used in StartPipe() should be random if( (i == 0) && (drawScheme == FRAME_SCHEME_CHASE) ) pNewPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST ); nPipesDrawn++; } // Increment scene rotation for normal reset case if( resetStatus & RESET_NORMAL_BIT ) view.IncrementSceneRotation(); // clear reset status resetStatus = 0; #ifdef DO_TIMING Timer( TIMER_START ); #endif } /**************************************************************************\ * CalcMaxPipesPerFrame * \**************************************************************************/ int STATE::CalcMaxPipesPerFrame() { int nCount=0, fCount=0; if( pFState ) fCount = pFState->GetMaxPipesPerFrame(); if( pNState ) nCount = bTexture ? NORMAL_TEX_PIPE_COUNT : NORMAL_PIPE_COUNT; return SS_MAX( nCount, fCount ); } /**************************************************************************\ * PickRandomTexture * * Pick a random texture index from a list. Remove entry from list as it * is picked. Once all have been picked, or starting a new frame, reset. * * ! Routine not reentrant, should only be called by the main thread * dispatcher (FrameReset) \**************************************************************************/ static int PickRandomTexture( int iThread, int nTextures ) { if( nTextures == 0 ) return 0; static int pickSet[MAX_TEXTURES] = {0}; static int nPicked = 0; int i, index; if( iThread == 0 ) // new frame - force reset nPicked = nTextures; // reset condition if( ++nPicked > nTextures ) { for( i = 0; i < nTextures; i ++ ) pickSet[i] = 0; nPicked = 1; // cuz } // Pick a random texture index index = ss_iRand( nTextures ); while( pickSet[index] ) { // this index has alread been taken, try the next one if( ++index >= nTextures ) index = 0; } // Hopefully, the above loop will exit :). This means that we have // found a texIndex that is available pickSet[index] = 1; // mark as taken return index; } /**************************************************************************\ * Clear * * Clear the screen. Depending on resetStatus, use normal clear or * fancy transitional clear. \**************************************************************************/ void STATE::Clear() { // clear the screen - any rc will do glClear(GL_DEPTH_BUFFER_BIT); if( resetStatus & RESET_RESIZE_BIT ) { // new window size - recalibrate the transitional clear // Calibration is set after a window resize, so window is already black ddClear.CalibrateClear( view.winSize.width, view.winSize.height, 2.0f ); } else if( resetStatus & RESET_NORMAL_BIT ) // do the normal transitional clear ddClear.Clear( view.winSize.width, view.winSize.height ); else { // do a fast one-shot clear glClear( GL_COLOR_BUFFER_BIT ); } } /**************************************************************************\ * DrawValidate * * Validation done before every Draw * * For now, this just involves checking resetStatus * \**************************************************************************/ void STATE::DrawValidate() { if( ! resetStatus ) return; FrameReset(); } /**************************************************************************\ * Draw * * - Top-level pipe drawing routine * - Each pipe thread keeps drawing new pipes until we reach maximum number * of pipes per frame - then each thread gets killed as soon as it gets * stuck. Once number of drawing threads reaches 0, we start a new * frame * \**************************************************************************/ void STATE::Draw(void *data) { int nKilledThreads = 0; BOOL bChooseNewLead = FALSE; // Validate the draw state DrawValidate(); // Check each pipe's status DRAW_THREAD *pThread = drawThreads; for( int i = 0; i < nDrawThreads; i++, pThread++ ) { if( pThread->pPipe->IsStuck() ) { if( ++nPipesDrawn > maxPipesPerFrame ) { // Reaching pipe saturation - kill this pipe thread if( (drawScheme == FRAME_SCHEME_CHASE) && (pThread->pPipe == pLeadPipe) ) bChooseNewLead = TRUE; pThread->KillPipe(); nKilledThreads++; } else { // Start up another pipe if( ! pThread->StartPipe() ) // we won't be able to draw any more pipes this frame // (probably out of nodes) maxPipesPerFrame = nPipesDrawn; } } } // Whenever one or more pipes are killed, compact the thread list if( nKilledThreads ) { CompactThreadList(); nDrawThreads -= nKilledThreads; } if( nDrawThreads == 0 ) { // This frame is finished - mark for reset on next Draw resetStatus |= RESET_NORMAL_BIT; return; } if( bChooseNewLead ) { // We're in 'chase mode' and need to pick a new lead pipe ChooseNewLeadPipe(); } // Draw each pipe for( i = 0, pThread = drawThreads; i < nDrawThreads; i++, pThread++ ) { pThread->DrawPipe(); #ifdef DO_TIMING pipeCount++; #endif } glFlush(); } /**************************************************************************\ * * CompactThreadList * * - Compact the thread list according to number of pipe threads killed * - The pipes have been killed, but the RC's in each slot are still valid * and reusable. So we swap up entries with valid pipes. This means that * the ordering of the RC's in the thread list will change during the life * of the program. This should be OK. * \**************************************************************************/ #define SWAP_SLOT( a, b ) \ DRAW_THREAD pTemp; \ pTemp = *(a); \ *(a) = *(b); \ *(b) = pTemp; void STATE::CompactThreadList() { if( nDrawThreads <= 1 ) // If only one active thread, it must be in slot 0 from previous // compactions - so nothing to do return; int iEmpty = 0; DRAW_THREAD *pThread = drawThreads; for( int i = 0; i < nDrawThreads; i ++, pThread++ ) { if( pThread->pPipe ) { if( iEmpty < i ) { // swap active pipe thread and empty slot SWAP_SLOT( &(drawThreads[iEmpty]), pThread ); } iEmpty++; } } } /**************************************************************************\ * * ChooseNewLeadPipe * * Choose a new lead pipe for chase mode. * \**************************************************************************/ void STATE::ChooseNewLeadPipe() { // Pick one of the active pipes at random to become the new lead int iLead = ss_iRand( nDrawThreads ); pLeadPipe = drawThreads[iLead].pPipe; pLeadPipe->SetChooseStartPosMethod( CHOOSE_STARTPOS_FURTHEST ); pLeadPipe->SetChooseDirectionMethod( CHOOSE_DIR_RANDOM_WEIGHTED ); } /******************************Public*Routine******************************\ * Finish * * - Called when GL window being closed * \**************************************************************************/ void STATE::Finish( void *data ) { delete (STATE *) data; } /**************************************************************************\ * DRAW_THREAD constructor * \**************************************************************************/ DRAW_THREAD::DRAW_THREAD() { hdc = 0; hglrc = 0; pPipe = NULL; htex = (HTEXTURE) -1; } /**************************************************************************\ * DRAW_THREAD destructor * * Delete any GL contexts * * - can't Delete shareRC, as this is done by common lib, so had to move * this up to ~STATE * \**************************************************************************/ DRAW_THREAD::~DRAW_THREAD() { #if 0 wglDeleteContext( hglrc ); #endif } /**************************************************************************\ * MakeRCCurrent * \**************************************************************************/ void DRAW_THREAD::MakeRCCurrent() { if( hglrc != wglGetCurrentContext() ) wglMakeCurrent( hdc, hglrc ); } /**************************************************************************\ * SetRCDC * \**************************************************************************/ void DRAW_THREAD::SetRCDC( HGLRC rc, HDC Hdc ) { hglrc = rc; hdc = Hdc; } /**************************************************************************\ * SetPipe * \**************************************************************************/ void DRAW_THREAD::SetPipe( PIPE *pipe ) { pPipe = pipe; } /**************************************************************************\ * HasRC * \**************************************************************************/ BOOL DRAW_THREAD::HasRC() { return( hglrc != 0 ); } /**************************************************************************\ * GetRC * \**************************************************************************/ HGLRC DRAW_THREAD::GetRC() { return hglrc; } /**************************************************************************\ * SetTexture * * - Set a texture for a thread * - Cache the texture index for performance \**************************************************************************/ void DRAW_THREAD::SetTexture( HTEXTURE hnewtex ) { if( hnewtex != htex ) { htex = hnewtex; ss_SetTexture( htex ); } } /**************************************************************************\ * DrawPipe * * - Draw pipe in thread slot, according to its type * \**************************************************************************/ void DRAW_THREAD::DrawPipe() { MakeRCCurrent(); switch( pPipe->type ) { case TYPE_NORMAL: ( (NORMAL_PIPE *) pPipe )->Draw(); break; case TYPE_FLEX_REGULAR: ( (REGULAR_FLEX_PIPE *) pPipe )->Draw(); break; case TYPE_FLEX_TURNING: ( (TURNING_FLEX_PIPE *) pPipe )->Draw(); break; } glFlush(); } /**************************************************************************\ * StartPipe * * Starts up pipe of the approppriate type. If can't find an empty node * for the pipe to start on, returns FALSE; * \**************************************************************************/ BOOL DRAW_THREAD::StartPipe() { MakeRCCurrent(); // call pipe-type specific Start function switch( pPipe->type ) { case TYPE_NORMAL: ( (NORMAL_PIPE *) pPipe )->Start(); break; case TYPE_FLEX_REGULAR: ( (REGULAR_FLEX_PIPE *) pPipe )->Start(); break; case TYPE_FLEX_TURNING: ( (TURNING_FLEX_PIPE *) pPipe )->Start(); break; } glFlush(); // check status if( pPipe->NowhereToRun() ) return FALSE; else return TRUE; } /**************************************************************************\ * KillPipe * \**************************************************************************/ void DRAW_THREAD::KillPipe() { switch( pPipe->type ) { case TYPE_NORMAL: delete (NORMAL_PIPE *) pPipe; break; case TYPE_FLEX_REGULAR: delete (REGULAR_FLEX_PIPE *) pPipe; break; case TYPE_FLEX_TURNING: delete (TURNING_FLEX_PIPE *) pPipe; break; } pPipe = NULL; }