//***************************************************************************************/
// 
// File name: Menu.cpp  
// Location: See Matrox Example Launcher in the MIL Control Center
// 
//
// Synopsis:  Implements the CMPMenu class.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved

#include "common.h"

//*****************************************************************************
// Constants.
//*****************************************************************************

//Button size
static const MIL_INT BUTTON_SIZE_X = 125;
static const MIL_INT BUTTON_SIZE_Y = 25;
static const MIL_INT SMALL_BUTTON_SIZE_X = 60;
static const MIL_INT LARGE_BUTTON_SIZE_X = 180;

//Button position
static const MIL_INT LINKED_BUTTON_GAP = 20;
static const MIL_INT BUTTON_GAP = 6;
static const MIL_INT BUTTON_START_X_POS = 240;
static const MIL_INT BUTTON_START_Y_POS = BUTTON_GAP;
static const MIL_INT SECTION_GAP = 16;

//Text position
static const MIL_INT TEXT_START_X = 15;
static const MIL_INT TEXT_OFFSET_Y = 8;

//Update information
static const MIL_INT UPDATE_INFO_TIME_DELAY = 1000;

//Menu 
static const MIL_INT MENU_SIZE_X = 650;
static const MIL_INT INITIAL_MENU_SIZE_Y = 520;
static const MIL_INT CORE_ROW_SIZE = BUTTON_SIZE_Y + BUTTON_GAP;
static const MIL_INT MAX_CORES_PER_ROW = 8;
static const MIL_DOUBLE BACKGROUND_COLOR = M_RGB888(255, 255, 255); 

//FPS information text 
static const MIL_DOUBLE INFO_COLOR = M_COLOR_DARK_BLUE;
static const MIL_INT INFO_TEXT_SIZE = 350;
static const MIL_INT TOTAL_FPS_GAP = 5;
static const MIL_INT PROC_FPS_SIZE = BUTTON_GAP*3;

//Other
static const MIL_INT APPLY_TO_ALL_THREADS_ADJUSTMENT = -2;
static const MIL_INT CORE_AFFINITY_GAP = BUTTON_GAP*5;


//*****************************************************************************
// Constructor. Allocates and intializes the menu.
//*****************************************************************************
CMPMenu::CMPMenu(MIL_INT ProcessingArraySize, CMPProcessing** Processing)
: m_ProcessingArraySize(ProcessingArraySize)
   {
   //Allocate memory to contain the processing objects
   m_Processing = new CMPProcessing*[m_ProcessingArraySize]; 

   //Get the number of cores available for the whole process
   MappInquireMp(M_DEFAULT, M_CORE_NUM_PROCESS, M_DEFAULT, M_DEFAULT, &m_NumberOfProcessorCores);

   m_NumberOfProcessorCores = (m_NumberOfProcessorCores<=MAX_CORES)?m_NumberOfProcessorCores:MAX_CORES;

   //Get the cores that are available for the process
   MIL_INT CoreAffinityMaskArraySize;
   MappInquireMp(M_DEFAULT, M_CORE_AFFINITY_MASK_ARRAY_SIZE, M_DEFAULT, M_DEFAULT, &CoreAffinityMaskArraySize);

   m_CoreAffinityMaskProcess = new MIL_UINT64[CoreAffinityMaskArraySize];

   MappInquireMp(M_DEFAULT, M_CORE_AFFINITY_MASK_PROCESS, M_DEFAULT, M_DEFAULT, m_CoreAffinityMaskProcess);

   //Set the number of cores to 1 if MP is disabled in MILConfig.
   if (MappInquireMp(M_DEFAULT, M_MP_FORCED_DISABLE, M_DEFAULT, M_DEFAULT, M_NULL)==M_YES) 
      m_NumberOfProcessorCores = 1;

   m_MPIsOff = (m_NumberOfProcessorCores<MIN_MP_CORES);

   MIL_INT NumCoreRows = (MIL_INT)ceil((MIL_DOUBLE)m_NumberOfProcessorCores/MAX_CORES_PER_ROW);
   m_MenuSizeY = INITIAL_MENU_SIZE_Y + ((NumCoreRows)*CORE_ROW_SIZE);

   //Initialize the number of memory banks
   m_NumMemoryBank = MappInquireMp(M_DEFAULT, M_MEMORY_BANK_NUM, M_DEFAULT, M_DEFAULT, M_NULL);

   //Keep a copy of the processing object pointers
   for (MIL_INT i=0; i<m_ProcessingArraySize; i++)
      {
      m_Processing[i] = Processing[i];
      }

   m_CurrentProcessing = 0;
   m_CurrentProcessingNum = 0;
   m_UpdateInfoStarted = false;

   //Set the initial state of the processing objects and run them
   if (m_MPIsOff)
      {
      //If there is no MP, disable it in the thread and run the processing.
      m_Processing[m_CurrentProcessingNum]->StartThread();
      m_Processing[m_CurrentProcessingNum]->SetMP(false);

      //Select and run the display
      m_Processing[m_CurrentProcessingNum]->DisplaySelect();
      m_Processing[m_CurrentProcessingNum]->RunDisplay(true);

      m_Processing[m_CurrentProcessingNum]->Run();

      //There is one processing thead
      m_CurrentProcessingNum = 1;
      }
   else
      {
      MIL_INT RunningThreadsNum = 0;
      MIL_INT NumCoresAssigned = 0;

      //Initially we have a maximum of two threads if there are at least 4 cores.  Otherwise,
      //run one thread and assign the cores to the thread because MP requires a minumum of two.
      if (m_NumberOfProcessorCores < 4)
         {
         RunningThreadsNum = 1;
         NumCoresAssigned = m_NumberOfProcessorCores;
         }
      else
         {
         RunningThreadsNum = 2;
         NumCoresAssigned = m_NumberOfProcessorCores>>1;
         }

      for (m_CurrentProcessingNum=0; m_CurrentProcessingNum<RunningThreadsNum; m_CurrentProcessingNum++)
         {
         //Start the threads
         m_Processing[m_CurrentProcessingNum]->StartThread();

         //Initialize the state of MP and assign half of available cores to each processing thread
         InitProcessingMP(NumCoresAssigned, m_Processing[m_CurrentProcessingNum]);

         //Select and run the display
         m_Processing[m_CurrentProcessingNum]->DisplaySelect();
         m_Processing[m_CurrentProcessingNum]->RunDisplay(true);

         //Run the processing
         m_Processing[m_CurrentProcessingNum]->Run();
         }
      }

   //Create the menu
   Create();

   //Start updating FPS info
   UpdateTotalFPS();
   UpdateCurProcFPS();

   for (MIL_INT i=0; i<m_ProcessingArraySize; i++)
      {
      //Start updating information on processing if any of the processing are running
      if (m_Processing[i]->IsRunning() && !m_UpdateInfoStarted)
         {
         StartUpdateInfo();
         }
      }

   //Update menu information so that it corresponds to the current processing state
   UpdateMenu();
   }

//*****************************************************************************
// Destructor. Frees the menu objects.
//*****************************************************************************
CMPMenu::~CMPMenu()
   {
   //Destroy the menu and free allocated memory
   Destroy();
   delete [] m_Processing;
   delete [] m_CoreAffinityMaskProcess;
   }

//*****************************************************************************
// Create.  Create the menu and show it.
//*****************************************************************************
void CMPMenu::Create()
   {
   //Allocate host system for the menu
   MsysAlloc(M_DEFAULT, M_SYSTEM_HOST, M_DEFAULT, M_DEFAULT, &m_MilMenuSystem);

   //Allocate MIL display for menu options
   MdispAlloc(m_MilMenuSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_WINDOWED, &m_MilMenuDisplay);

   //Allocate the buffer
   MbufAllocColor(m_MilMenuSystem, 3, MENU_SIZE_X, m_MenuSizeY, 8+M_UNSIGNED, M_IMAGE+M_PROC+M_DISP, &m_MilMenuBuffer);
   MbufClear(m_MilMenuBuffer, BACKGROUND_COLOR);

   //Setup graphics context for menu text
   MgraAlloc(m_MilMenuSystem, &m_MilMenuGraphicsContext);
   MgraControl(m_MilMenuGraphicsContext, M_BACKGROUND_MODE, M_TRANSPARENT);
   MgraColor(m_MilMenuGraphicsContext, INFO_COLOR);

   //Setup graphics context for showing frames per second on the menu
   MgraAlloc(m_MilMenuSystem, &m_MilInfoGraphicsContext);
   MgraControl(m_MilInfoGraphicsContext, M_BACKGROUND_MODE, M_TRANSPARENT);
   MgraControl(m_MilInfoGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilInfoGraphicsContext, NORMAL_FONT_TYPE);
   MgraColor(m_MilInfoGraphicsContext, INFO_COLOR);

   //Setup the menu options
   CreateMenu(); 

   //Setup the display of the menu
   MdispControl(m_MilMenuDisplay, M_KEYBOARD_USE, M_DISABLE);
   MdispControl(m_MilMenuDisplay, M_MOUSE_USE, M_DISABLE);
   MdispControl(m_MilMenuDisplay, M_WINDOW_RESIZE, M_DISABLE);
#if M_MIL_USE_WINDOWS
   MdispControl(m_MilMenuDisplay, M_WINDOW_SYSBUTTON, M_DISABLE);
#endif
   MdispControl(m_MilMenuDisplay, M_TITLE, MIL_TEXT("Multiprocessing"));
   MdispControl(m_MilMenuDisplay, M_WINDOW_TITLE_BAR_CHANGE, M_DISABLE); 

   MdispControl(m_MilMenuDisplay, M_WINDOW_OVERLAP, M_DISABLE);

   //Allocate the thread that updates FPS
   MthrAlloc (m_MilMenuSystem, M_EVENT, M_NOT_SIGNALED+M_AUTO_RESET, M_NULL, M_NULL, &m_MilInfoEvents[enRun]);
   MthrAlloc (m_MilMenuSystem, M_EVENT, M_NOT_SIGNALED+M_AUTO_RESET, M_NULL, M_NULL, &m_MilInfoEvents[enKill]);

   MthrAlloc (m_MilMenuSystem, M_THREAD, M_DEFAULT, &UpdateInfoFunction, (void*)this, &m_MilInfoThread);

   MthrAlloc (m_MilMenuSystem, M_EVENT, M_NOT_SIGNALED+M_AUTO_RESET, M_NULL, M_NULL, &m_MilUpdateInfoStoppedEvent);
   }

//*****************************************************************************
// Destroy.  Destroy the allocated menu objects.
//*****************************************************************************
void CMPMenu::Destroy()
   {
   //Pause the processing
   for (MIL_INT i=0; i<m_ProcessingArraySize; i++)
      m_Processing[i]->Pause();

   //Stop and kill the update info thread
   StopUpdateInfo();
   MthrControl(m_MilInfoEvents[enKill], M_EVENT_SET, M_SIGNALED);
   MthrWait(m_MilInfoThread, M_THREAD_END_WAIT, M_NULL);

   //Free all menu objects
   MthrFree(m_MilInfoEvents[enRun]);
   MthrFree(m_MilInfoEvents[enKill]);
   MthrFree(m_MilInfoThread);
   MthrFree(m_MilUpdateInfoStoppedEvent);

   //Unhook the function from the menu display
   MdispHookFunction(m_MilMenuDisplay, M_MOUSE_LEFT_BUTTON_UP+M_UNHOOK, MenuHookFct, (void*)this);

   //Deselect the display
   MdispSelect(m_MilMenuDisplay, M_NULL);

   //Free the rest of the menu objects
   for (MIL_INT i=FIRST_INFO; i<=LAST_INFO; i++)
      {
      MbufFree(m_InfoRegions[i].MilRectBuffer);
      }

   for (MIL_INT i=FIRST_BUTTON; i<=LAST_BUTTON; i++)
      {
      m_Buttons[i].Destroy();
      }

   MbufFree(m_MilMenuBuffer);
   MdispFree(m_MilMenuDisplay);  
   MgraFree(m_MilInfoGraphicsContext);
   MgraFree(m_MilMenuGraphicsContext);
   MsysFree(m_MilMenuSystem);
   }

//******************************************************************************************
// SetupMenu.  Write the menu options in the buffer and associate the options to functions.
//******************************************************************************************
void CMPMenu::CreateMenu()
   {
   MIL_INT ButtonStartX = BUTTON_START_X_POS;
   MIL_INT ButtonStartY = BUTTON_START_Y_POS;
   
   //Create the section with general application settings such as number of threads
   //and whether or not to show the displays.
   CreateApplicationControlSection(ButtonStartX, ButtonStartY);

   //Create the section with thread selection and configuration
   CreateUserThreadsSection(ButtonStartX, ButtonStartY);

   //Create the section with MP configuration
   CreateMPSection(ButtonStartX, ButtonStartY);

   //Hook functions to mouse events
   MdispHookFunction(m_MilMenuDisplay, M_MOUSE_LEFT_BUTTON_UP, MenuHookFct, (void*)this);
   }

//******************************************************************************************
// CreateApplicationControlSection.  Setup the user thread control menu options.
//******************************************************************************************
void CMPMenu::CreateApplicationControlSection(MIL_INT& ButtonStartX, MIL_INT& ButtonStartY)
   {
   //Set the section heading
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, LARGE_FONT);

   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);

   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MgraFont(m_MilMenuGraphicsContext, BOLD_FONT_TYPE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MIL_TEXT("Application Control"));

   MoveXY(ButtonStartX, ButtonStartY, 0, SECTION_GAP);

   //Create menu buttons
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, 
      MIL_TEXT("Number of User Threads"));

   MIL_INT LinkedStartX = ButtonStartX;
   MIL_INT LinkedStartY = ButtonStartY;
   CreateButton(enTotalThreadsLeft, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" <<"), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");
   CreateButton(enTotalThreads, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enTotalThreads, ButtonText), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enTotalThreadsRight, LinkedStartX, ButtonStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" >>"), false);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Show Displays"));

   CreateButton(enShowDisplay, ButtonStartX, ButtonStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enShowDisplay, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   //Create region for total FPS
   m_InfoRegions[enTotalFPS].StartX = TEXT_START_X;
   m_InfoRegions[enTotalFPS].StartY = ButtonStartY + TOTAL_FPS_GAP-1;
   m_InfoRegions[enTotalFPS].EndX = m_InfoRegions[enTotalFPS].StartX + INFO_TEXT_SIZE-1;
   m_InfoRegions[enTotalFPS].EndY = m_InfoRegions[enTotalFPS].StartY + PROC_FPS_SIZE-1;

   MbufChild2d(m_MilMenuBuffer, 
               m_InfoRegions[enTotalFPS].StartX, m_InfoRegions[enTotalFPS].StartY, 
               m_InfoRegions[enTotalFPS].EndX-m_InfoRegions[enTotalFPS].StartX+1, 
               m_InfoRegions[enTotalFPS].EndY-m_InfoRegions[enTotalFPS].StartY+1, 
               &m_InfoRegions[enTotalFPS].MilRectBuffer);

   ButtonStartY = m_InfoRegions[enTotalFPS].EndY + TOTAL_FPS_GAP-1;

   MgraRectFill(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MENU_SIZE_X-TEXT_START_X, ButtonStartY+2);

   MoveXY(ButtonStartX, ButtonStartY, 0, SECTION_GAP);
   }


//******************************************************************************************
// CreateUserThreadsSection.  Setup the menu options related to the main processing thread.
//******************************************************************************************
void CMPMenu::CreateUserThreadsSection(MIL_INT& ButtonStartX, MIL_INT& ButtonStartY)
   {
   //Set the section heading
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, LARGE_FONT);

   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);

   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MgraFont(m_MilMenuGraphicsContext, BOLD_FONT_TYPE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MIL_TEXT("User Thread Selection"));

   MoveXY(ButtonStartX, ButtonStartY, 0, SECTION_GAP);

   //Create menu buttons
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Thread Index"));

   MIL_INT LinkedStartX = ButtonStartX;
   MIL_INT LinkedStartY = ButtonStartY;
   CreateButton(enProcThreadLeft, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" <<"), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");
   CreateButton(enProcThread, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enProcThread, ButtonText), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enProcThreadRight, LinkedStartX, ButtonStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" >>"), false);

   MoveXY(LinkedStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   MgraRectFill(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MENU_SIZE_X-TEXT_START_X, ButtonStartY+1);

   MoveXY(LinkedStartX, ButtonStartY, 0, BUTTON_GAP);

   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, LARGE_FONT);

   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);

   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MgraFont(m_MilMenuGraphicsContext, BOLD_FONT_TYPE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MIL_TEXT("Selected Thread Control"));

   MoveXY(ButtonStartX, ButtonStartY, 0, APPLY_TO_ALL_THREADS_ADJUSTMENT);

   CreateButton(enApplyToAllThreads, LinkedStartX, ButtonStartY, LARGE_BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enApplyToAllThreads, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Run Processing"));

   CreateButton(enRunProcessing, ButtonStartX, ButtonStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enRunProcessing, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Refresh Display"));

   CreateButton(enRunDisplay, ButtonStartX, ButtonStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enRunDisplay, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);
   }

//******************************************************************************************
// CreateBufferSection.  Setup the menu options related to buffer allocation.
//******************************************************************************************
void CMPMenu::CreateBufferSection(MIL_INT& ButtonStartX, MIL_INT& ButtonStartY)
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Set information on memory bank
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, 
      MIL_TEXT("Memory Bank Affinity"));

   //Create memory bank buttons
   MIL_INT LinkedStartX = ButtonStartX;
   MIL_INT LinkedStartY = ButtonStartY;
   CreateButton(enMemoryBankLeft, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" <<"), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enMemoryBank, LinkedStartX, LinkedStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enMemoryBank, ButtonText), false);

   if (m_MPIsOff)
      m_Buttons[enMemoryBank].Disable(MIL_TEXT("n/a"));

   MoveXY(LinkedStartX, LinkedStartY, BUTTON_SIZE_X + LINKED_BUTTON_GAP, 0);

   CreateButton(enMemoryBankRight, LinkedStartX, ButtonStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" >>"), false);

   MoveXY(LinkedStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);
   }

//******************************************************************************************
// CreateMPSection.  Setup the options related to MP control.
//******************************************************************************************
void CMPMenu::CreateMPSection(MIL_INT& ButtonStartX, MIL_INT& ButtonStartY)
   {
   MIL_INT LinkedStartX = 0; 
   MIL_INT LinkedStartY = 0;
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");   

   //Set multiprocessing information and buttons
   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, 
      MIL_TEXT("Use Multiprocessing"));

   CreateButton(enMP, ButtonStartX, ButtonStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enMP, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP+SECTION_GAP);

   if (m_MPIsOff)
      m_Buttons[enMP].Disable(MIL_TEXT("n/a"));

   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, MEDIUM_FONT);
   MgraFont(m_MilMenuGraphicsContext, M_FONT_DEFAULT);

   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MgraFont(m_MilMenuGraphicsContext, BOLD_FONT_TYPE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY, MIL_TEXT("Multiprocessing Control"));

   MoveXY(ButtonStartX, ButtonStartY, 0, SECTION_GAP);

   MgraControl(m_MilMenuGraphicsContext, M_FONT_SIZE, SMALL_FONT);
   MgraFont(m_MilMenuGraphicsContext, NORMAL_FONT_TYPE);
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Maximum Cores"));

   LinkedStartX = ButtonStartX;
   LinkedStartY = ButtonStartY;
   CreateButton(enCoreMaxLeft, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" <<"), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enCoreMax, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enCoreMax, ButtonText), false);

   if (m_MPIsOff)
      m_Buttons[enCoreMax].Disable(MIL_TEXT("n/a"));

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enCoreMaxRight, LinkedStartX, ButtonStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" >>"), false);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Core Sharing"));

   CreateButton(enCoreSharing, ButtonStartX, ButtonStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enCoreSharing, ButtonText), true);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   if (m_MPIsOff)
      m_Buttons[enCoreSharing].Disable(MIL_TEXT("n/a"));

   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, MIL_TEXT("Priority"));

   LinkedStartX = ButtonStartX;
   LinkedStartY = ButtonStartY;
   CreateButton(enMPPriorityLeft, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(" <<"), false);

   MoveXY(LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enMPPriority, LinkedStartX, LinkedStartY, BUTTON_SIZE_X, BUTTON_SIZE_Y, 
      GetButtonText(m_CurrentProcessing, enMPPriority, ButtonText), false);

   if (m_MPIsOff)
      m_Buttons[enMPPriority].Disable(MIL_TEXT("n/a"));

   MoveXY(LinkedStartX, LinkedStartY, BUTTON_SIZE_X+LINKED_BUTTON_GAP, 0);

   CreateButton(enMPPriorityRight, LinkedStartX, ButtonStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, MIL_TEXT(">>"), false);

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_SIZE_Y+BUTTON_GAP);

   //Create section for buffer memory banks
   CreateBufferSection(ButtonStartX, ButtonStartY);

   //Create the core affinity buttons
   MgraText(m_MilMenuGraphicsContext, m_MilMenuBuffer, TEXT_START_X, ButtonStartY+TEXT_OFFSET_Y, 
      MIL_TEXT("Core Affinity: (Core Index, Local Memory Bank Index)"));

   MoveXY(ButtonStartX, ButtonStartY, 0, CORE_AFFINITY_GAP);

   LinkedStartX = TEXT_START_X; 
   ButtonStartX = LinkedStartX;
   LinkedStartY = ButtonStartY;

   //Create one button for each core
   for (MIL_INT i=FIRST_CORE, Count=0; (i-FIRST_CORE)<m_NumberOfProcessorCores; i++, Count++)
      {      
      CreateButton((MPButtons)i, LinkedStartX, LinkedStartY, SMALL_BUTTON_SIZE_X, BUTTON_SIZE_Y, 
         GetButtonText(m_CurrentProcessing, (MPButtons)i, ButtonText), true);      

      if (m_MPIsOff)
         m_Buttons[(MPButtons)i].Disable(MIL_TEXT("n/a"));

      LinkedStartX = ButtonStartX + ( ((Count+1)%MAX_CORES_PER_ROW) * (SMALL_BUTTON_SIZE_X + LINKED_BUTTON_GAP) );
      LinkedStartY = ButtonStartY + ( ((Count+1)/MAX_CORES_PER_ROW) * (BUTTON_GAP + BUTTON_SIZE_Y));
      }

   //Create region for FPS update information
   ButtonStartX = BUTTON_START_X_POS;

   MoveXY(ButtonStartX, ButtonStartY, 0, BUTTON_GAP + SECTION_GAP);

   m_InfoRegions[enCurProcFPS].StartX = TEXT_START_X;
   m_InfoRegions[enCurProcFPS].StartY = m_MenuSizeY - PROC_FPS_SIZE;
   m_InfoRegions[enCurProcFPS].EndX = m_InfoRegions[enCurProcFPS].StartX + INFO_TEXT_SIZE-1;
   m_InfoRegions[enCurProcFPS].EndY = m_InfoRegions[enCurProcFPS].StartY + PROC_FPS_SIZE-1; 

   MbufChild2d(m_MilMenuBuffer, 
               m_InfoRegions[enCurProcFPS].StartX, m_InfoRegions[enCurProcFPS].StartY, 
               m_InfoRegions[enCurProcFPS].EndX-m_InfoRegions[enCurProcFPS].StartX+1, 
               m_InfoRegions[enCurProcFPS].EndY-m_InfoRegions[enCurProcFPS].StartY+1,
               &m_InfoRegions[enCurProcFPS].MilRectBuffer);
   }

//*******************************************************************************
// CreateButton.  Setup menu button with given information.
//*******************************************************************************
void CMPMenu::CreateButton(MPButtons ButtonType, MIL_INT StartX, MIL_INT StartY, 
                           MIL_INT ButtonSizeX, MIL_INT ButtonSizeY, 
                           MIL_CONST_TEXT_PTR ButtonText, bool IsToggle)
   {
   RectStruct ButtonRect;

   //Define the button rectangle
   ButtonRect.StartX = StartX;
   ButtonRect.EndX = ButtonRect.StartX + ButtonSizeX-1;
   ButtonRect.StartY = StartY;
   ButtonRect.EndY = ButtonRect.StartY + ButtonSizeY-1;

   //Create the button on the menu buffer
   m_Buttons[ButtonType].Create(ButtonText, m_MilMenuBuffer, ButtonRect, IsToggle);
   }

//*******************************************************************************
// MoveXY.  Move the provided x,y positions by the given offsets.
//*******************************************************************************
void CMPMenu::MoveXY(MIL_INT& StartX, MIL_INT& StartY, MIL_INT OffsetX, MIL_INT OffsetY)
   {
   StartX += OffsetX;
   StartY += OffsetY;
   }


//*******************************************************************************
// Run.  Run the menu
//*******************************************************************************
void CMPMenu::Run()
   {
   //Show the menu
   MdispSelect(m_MilMenuDisplay, m_MilMenuBuffer);
   }

//*******************************************************************************
// GetButton.  Return the button that corresponds to the given x,y position
//*******************************************************************************
CMPMenu::MPButtons CMPMenu::GetButton(MIL_INT PositionX, MIL_INT PositionY)
   {
   MPButtons Button= (MPButtons)-1;

   for (MIL_INT i=FIRST_BUTTON; i<=LAST_BUTTON; i++)
      {
      //Get the button index that falls within the region the user just clicked on
      if (  (m_Buttons[i].GetRect().StartX<=PositionX) &&
            (m_Buttons[i].GetRect().EndX>=PositionX)   &&
            (m_Buttons[i].GetRect().StartY<=PositionY) &&
            (m_Buttons[i].GetRect().EndY>=PositionY) )
         {
         Button = (MPButtons)i;
         break;
         }
      }

   return Button;
   }

//*******************************************************************************
// ProcessingButtonClick.  Defines start/stop button operation.
//*******************************************************************************
void CMPMenu::ProcessingButtonClick()
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Pause or run the button depending on the current state
   if (m_Processing[m_CurrentProcessing]->IsRunning())
      {
      m_Processing[m_CurrentProcessing]->Pause();
      StopUpdateInfo();
      }
   else
      {
      m_Processing[m_CurrentProcessing]->Run();
      StartUpdateInfo();
      }

   //Update the button and the menu
   m_Buttons[enRunProcessing].Push(GetButtonText(m_CurrentProcessing, enRunProcessing, ButtonText));

   UpdateMenu();
   }

//*******************************************************************************
// MPButtonClick.  Defines MP button operation.
//*******************************************************************************
void CMPMenu::MPButtonClick()
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Set current MP status and update the button
   if (m_Processing[m_CurrentProcessing]->MPEnabled())
      {
      m_Processing[m_CurrentProcessing]->SetMP(false);
      }
   else
      {
      m_Processing[m_CurrentProcessing]->SetMP(true);
      }
   m_Buttons[enMP].Push(GetButtonText(m_CurrentProcessing, enMP, ButtonText));
   }

//*******************************************************************************
// CoreMaxButtonClick.  Defines core maximum add button operation.
//*******************************************************************************
void CMPMenu::CoreMaxButtonClick(bool Add)
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   MIL_INT CurrentCoreMax = m_Processing[m_CurrentProcessing]->GetCoreMax();

   //Increase or reduce the maximum cores for currently selected processing
   if (Add && (CurrentCoreMax < m_NumberOfProcessorCores))
      {
      CurrentCoreMax++;
      }
   else if (!Add && (CurrentCoreMax>2))
      {
      CurrentCoreMax--;
      }
   m_Processing[m_CurrentProcessing]->SetCoreMax(CurrentCoreMax);
   m_Buttons[enCoreMax].Push(GetButtonText(m_CurrentProcessing, enCoreMax, ButtonText));
   }


//*******************************************************************************
// CoreSharingButtonClick.  Defines core sharing button operation.
//*******************************************************************************
void CMPMenu::CoreSharingButtonClick()
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Enable or disable core sharing
   if (m_Processing[m_CurrentProcessing]->CoreSharingEnabled())
      {
      m_Processing[m_CurrentProcessing]->SetCoreSharing(false);
      }
   else
      {
      m_Processing[m_CurrentProcessing]->SetCoreSharing(true);
      }
   m_Buttons[enCoreSharing].Push(GetButtonText(m_CurrentProcessing, enCoreSharing, ButtonText));
   }

//*******************************************************************************
// MPPriorityButtonClick.  Defines MP priority button operation.
//*******************************************************************************
void CMPMenu::MPPriorityButtonClick(bool Next)
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");
   MIL_INT MPPriority = m_Processing[m_CurrentProcessing]->GetMPPriority();

   //Set the priority for the currently selected processing thread
   switch (MPPriority)
      {
      case M_ABOVE_NORMAL:
         if (Next)
            MPPriority=M_BELOW_NORMAL;
         break;
      case M_BELOW_NORMAL:
         if (Next)
            MPPriority=M_HIGHEST;
         else
            MPPriority=M_ABOVE_NORMAL;
         break;
      case M_HIGHEST:
         if (Next)
            MPPriority=M_IDLE;
         else
            MPPriority=M_BELOW_NORMAL;
         break;
      case M_IDLE:
         if (Next)
            MPPriority=M_LOWEST;
         else
            MPPriority=M_HIGHEST;
         break;
      case M_LOWEST:
         if (Next)
            MPPriority=M_NORMAL;
         else
            MPPriority=M_IDLE;
         break;
      case M_NORMAL:
         if (Next)
            MPPriority=M_TIME_CRITICAL;
         else
            MPPriority=M_LOWEST;
         break;
      case M_TIME_CRITICAL:
         if (!Next)
            MPPriority=M_NORMAL;
         break;
      default:
         break;
      }

   m_Processing[m_CurrentProcessing]->SetMPPriority(MPPriority);
   m_Buttons[enMPPriority].Push(GetButtonText(m_CurrentProcessing, enMPPriority, ButtonText));
   }

//*******************************************************************************
// ProcessingThreadButtonClick.  Select the processing thread to control.
//*******************************************************************************
void CMPMenu::ProcessingThreadButtonClick(bool Next)
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Select the requested processing thread and update information on the menu
   //accordingly
   if (Next && (m_CurrentProcessing < m_CurrentProcessingNum-1))
      {
      m_CurrentProcessing++;
      }
   else if (!Next && (m_CurrentProcessing > 0))
      {
      m_CurrentProcessing--;      
      }

   m_Buttons[enProcThread].Push(GetButtonText(m_CurrentProcessing, enProcThread, ButtonText));

   UpdateMenu();
   }

//*******************************************************************************
// ApplyToAllThreadsButtonClick.  Apply current thread settings to all threads
//*******************************************************************************
void CMPMenu::ApplyToAllThreadsButtonClick()
   {
   //Apply current processing configuration to all threads
   MIL_INT ThreadIndex;  
   bool UseMemoryBank = m_Processing[m_CurrentProcessing]->UseMemoryBank();
   MIL_INT64 MemoryBank = m_Processing[m_CurrentProcessing]->GetCurrentMemoryBank();
   for (MIL_INT i=1; i<m_CurrentProcessingNum; i++)
      {
      ThreadIndex = (m_CurrentProcessing+i)%(m_CurrentProcessingNum);
      if (m_Processing[m_CurrentProcessing]->IsRunning())
         m_Processing[ThreadIndex]->Run();
      else
         m_Processing[ThreadIndex]->Pause();

      m_Processing[ThreadIndex]->RunDisplay(m_Processing[m_CurrentProcessing]->DisplayRunning());
      m_Processing[ThreadIndex]->SetMP(m_Processing[m_CurrentProcessing]->MPEnabled());
      m_Processing[ThreadIndex]->SetCoreMax(m_Processing[m_CurrentProcessing]->GetCoreMax());
      m_Processing[ThreadIndex]->SetCoreSharing(m_Processing[m_CurrentProcessing]->CoreSharingEnabled());
      m_Processing[ThreadIndex]->SetMPPriority(m_Processing[m_CurrentProcessing]->GetMPPriority());
      m_Processing[ThreadIndex]->SetCoreAffinity(m_Processing[m_CurrentProcessing]->GetCoreAffinity());

      m_Processing[ThreadIndex]->SetCurrentMemoryBank(MemoryBank, UseMemoryBank);
      }

   //For visual effect, push the button twice.
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");
   m_Buttons[enApplyToAllThreads].Push(ButtonText);
   MosSleep(50);
   GetButtonText(m_CurrentProcessing, enApplyToAllThreads, ButtonText);
   m_Buttons[enApplyToAllThreads].Push(GetButtonText(m_CurrentProcessing, enApplyToAllThreads, ButtonText));
   }

//*******************************************************************************
// NumberOfThreadsButtonClick.  Select the number of processing threads.
//*******************************************************************************
void CMPMenu::NumberOfThreadsButtonClick(bool Next)
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Increase or reduce the number of processing threads
   if (Next && (m_CurrentProcessingNum < m_ProcessingArraySize))
      {
      m_CurrentProcessingNum++;

      //Start and configure the thread
      m_Processing[m_CurrentProcessingNum-1]->StartThread();  

      if (m_Processing[m_CurrentProcessing]->DisplaySelected())
         {
         m_Processing[m_CurrentProcessingNum-1]->DisplaySelect();
         m_Processing[m_CurrentProcessingNum-1]->RunDisplay(true);
         }
      else
         {
         m_Processing[m_CurrentProcessingNum-1]->RunDisplay(false);
         }

      InitProcessingMP(m_NumberOfProcessorCores, m_Processing[m_CurrentProcessingNum-1]);

      m_Processing[m_CurrentProcessingNum-1]->Run();

      m_CurrentProcessing = m_CurrentProcessingNum-1;

      StartUpdateInfo();
      }
   else if (!Next && (m_CurrentProcessingNum > 1))
      {
      //Stop the thread
      m_Processing[m_CurrentProcessingNum-1]->DisplayDeselect();      
      m_Processing[m_CurrentProcessingNum-1]->StopThread();
      m_CurrentProcessingNum--;

      if (m_CurrentProcessing == m_CurrentProcessingNum)
         m_CurrentProcessing = m_CurrentProcessingNum-1;

      StopUpdateInfo();
      }

   //Update button and menu accordingly
   m_Buttons[enTotalThreads].Push(GetButtonText(m_CurrentProcessing, enTotalThreads, ButtonText));
   UpdateMenu();
   }

//*******************************************************************************
// CurrentThreadDisplayButtonClick.  Defines selected thread display button operation.
//*******************************************************************************
void CMPMenu::CurrentThreadDisplayButtonClick()
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Set whether to run or stop the display update of the currently selected
   //processing thread.
   if (m_Processing[m_CurrentProcessing]->DisplayRunning())
      {
      m_Processing[m_CurrentProcessing]->RunDisplay(false);
      }
   else
      {
      m_Processing[m_CurrentProcessing]->RunDisplay(true);
      }

   m_Buttons[enRunDisplay].Push(GetButtonText(m_CurrentProcessing, enRunDisplay, ButtonText));
   }

//*******************************************************************************
// AllThreadsDisplayButtonClick.  Defines display button operation for all 
// threads.
//*******************************************************************************
void CMPMenu::AllThreadsDisplayButtonClick()
   {
   //Either show or remove all currently allocated threads
   if (m_Processing[m_CurrentProcessing]->DisplaySelected())
      {
      for (MIL_INT i=0; i<m_CurrentProcessingNum; i++)
         {
         //Pause the display if it is running
         if (m_Processing[i]->DisplayRunning())
            m_Processing[i]->RunDisplay(false);

         //Deselect the display
         m_Processing[i]->DisplayDeselect();
         }
      }
   else
      {
      for (MIL_INT i=0; i<m_CurrentProcessingNum; i++)
         {
         //Run the display
         m_Processing[i]->RunDisplay(true);

         //Select the display
         m_Processing[i]->DisplaySelect();
         }
      }

   UpdateMenu();
   }

//*******************************************************************************
// MemoryBankButtonClick.  Select the memory bank to allocate processing buffers 
// on.
//*******************************************************************************
void CMPMenu::MemoryBankButtonClick(bool Next)
   {      
   //Set the current memory bank to use.  Initially, no memory bank is specified.
   if ( (!m_Processing[m_CurrentProcessing]->UseMemoryBank() && Next) && (m_NumMemoryBank>0) )
      {
      m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_0, true);
      }
   else if (m_Processing[m_CurrentProcessing]->UseMemoryBank())
      {
      bool ValidBank = false;
      MIL_INT64 NewMemoryBank = m_Processing[m_CurrentProcessing]->GetMemoryBank(Next, ValidBank);

      if (ValidBank)
         {
         switch(NewMemoryBank)
            {
            case M_MEMORY_BANK_0:
               if (!Next && (m_Processing[m_CurrentProcessing]->GetCurrentMemoryBank()==M_MEMORY_BANK_0))
                  {
                  m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(0, false);
                  }
               else if (!Next)
                  {
                  m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_0, true);
                  }
               break;
            case M_MEMORY_BANK_1:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_1, true);
               break;
            case M_MEMORY_BANK_2:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_2, true);
               break;
            case M_MEMORY_BANK_3:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_3, true);
               break;
            case M_MEMORY_BANK_4:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_4, true);
               break;
            case M_MEMORY_BANK_5:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_5, true);
               break;
            case M_MEMORY_BANK_6:
               m_Processing[m_CurrentProcessing]->SetCurrentMemoryBank(M_MEMORY_BANK_6, true);
               break;
            default:
               break;
            }
         }
      }

   //Update the button text
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");
   m_Buttons[enMemoryBank].Push(GetButtonText(m_CurrentProcessing, enMemoryBank, ButtonText));
   }

//*******************************************************************************
// CoreAffinityButtonClick.  Set or remove an affinity to the given core index.
//*******************************************************************************
void CMPMenu::CoreAffinityButtonClick(MIL_INT CoreIndex)
   {
   MIL_UINT64 CurrentCoreAffinity = m_Processing[m_CurrentProcessing]->GetCoreAffinity();
   MIL_UINT64 CoreAffinityMask = 0x00000001;

   //Set the mask on the position that represents the index of the real core
   CoreAffinityMask = CoreAffinityMask << GetRealCoreIndex(CoreIndex);

   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   if (m_Buttons[CoreIndex+FIRST_CORE].IsPressed())
      {
      //remove affinity
      m_Processing[m_CurrentProcessing]->SetCoreAffinity(CurrentCoreAffinity&~CoreAffinityMask);
      }
   else
      {
      //set affinity
      m_Processing[m_CurrentProcessing]->SetCoreAffinity(CurrentCoreAffinity|CoreAffinityMask);
      }

   m_Buttons[CoreIndex+FIRST_CORE].Push(GetButtonText(m_CurrentProcessing, (MPButtons)(CoreIndex+FIRST_CORE), 
      ButtonText));   
   }

//*******************************************************************************
// UpdateMenu.  Updated the status of all the buttons and info if processing
// thread has changed.
//*******************************************************************************
void CMPMenu::UpdateMenu()
   {
   MIL_TEXT_CHAR ButtonText[STRING_SIZE]=MIL_TEXT("");

   //Make sure the display selection is consistent with the show display button
   if ( (m_Processing[m_CurrentProcessing]->DisplaySelected() && !m_Buttons[enShowDisplay].IsPressed()) ||
        (!m_Processing[m_CurrentProcessing]->DisplaySelected() && m_Buttons[enShowDisplay].IsPressed()) )
      {
      m_Buttons[enShowDisplay].Push(GetButtonText(m_CurrentProcessing, enShowDisplay, ButtonText));
      }

   //Update the currently selected processing index
   m_Buttons[enProcThread].Push(GetButtonText(m_CurrentProcessing, enProcThread, ButtonText));

   //Update whether it is running or not and it's associated FPS
   if (m_Processing[m_CurrentProcessing]->IsRunning() && !m_Buttons[enRunProcessing].IsPressed())
      {
      m_Buttons[enRunProcessing].Push(GetButtonText(m_CurrentProcessing, enRunProcessing, ButtonText));
      }
   else if (!m_Processing[m_CurrentProcessing]->IsRunning())
      {
      UpdateCurProcFPS();
      if (m_Buttons[enRunProcessing].IsPressed())
         m_Buttons[enRunProcessing].Push(GetButtonText(m_CurrentProcessing, enRunProcessing, ButtonText));
      }

   //Clear total FPS if none of the threads are processing
   MIL_INT i=0;
   for (i=0; i<m_CurrentProcessingNum; i++)
      {
      if (m_Processing[i]->IsRunning())
         break;
      }

   if (i==m_CurrentProcessingNum)
      UpdateTotalFPS();

   //Update the update display button
   if ( (m_Processing[m_CurrentProcessing]->DisplayRunning() && !m_Buttons[enRunDisplay].IsPressed()) ||
        (!m_Processing[m_CurrentProcessing]->DisplayRunning() && m_Buttons[enRunDisplay].IsPressed()) )
      {
      m_Buttons[enRunDisplay].Push(GetButtonText(m_CurrentProcessing, enRunDisplay, ButtonText));
      }

   if (!m_MPIsOff)
      {
      //Update the MP button
      if ( (!m_Processing[m_CurrentProcessing]->MPEnabled() && m_Buttons[enMP].IsPressed()) || 
           (m_Processing[m_CurrentProcessing]->MPEnabled() && !m_Buttons[enMP].IsPressed()) )
         {
         m_Buttons[enMP].Push(GetButtonText(m_CurrentProcessing, enMP, ButtonText));
         }

      //Update maximum cores button
      m_Buttons[enCoreMax].Push(GetButtonText(m_CurrentProcessing, enCoreMax, ButtonText));

      //Update MP thread Priority
      m_Buttons[enMPPriority].Push(GetButtonText(m_CurrentProcessing, enMPPriority, ButtonText));
      
      //Update Memory bank button
      m_Buttons[enMemoryBank].Push(GetButtonText(m_CurrentProcessing, enMemoryBank, ButtonText));

      //Update Core sharing button
      if ( (!m_Processing[m_CurrentProcessing]->CoreSharingEnabled() && m_Buttons[enCoreSharing].IsPressed()) || 
           (m_Processing[m_CurrentProcessing]->CoreSharingEnabled() && !m_Buttons[enCoreSharing].IsPressed()) )
         {
         m_Buttons[enCoreSharing].Push(GetButtonText(m_CurrentProcessing, enCoreSharing, ButtonText));
         }

      //Update Core affinity buttons
      MIL_UINT64 CurrentCoreAffinity = m_Processing[m_CurrentProcessing]->GetCoreAffinity();

      for (MIL_INT i=FIRST_CORE; (i-FIRST_CORE)<m_NumberOfProcessorCores; i++)
         {  
         bool AffinityIsSet = (CurrentCoreAffinity&((MIL_UINT64)1<<GetRealCoreIndex(i-FIRST_CORE))) != 0x00000000;

         if (AffinityIsSet != m_Buttons[i].IsPressed())
            {
            m_Buttons[i].Push(GetButtonText(m_CurrentProcessing, (MPButtons)i, ButtonText));
            }
         }
      }
   }

//*******************************************************************************
// UpdateInfo.  Update information such as FPS the menu display.
//*******************************************************************************
void CMPMenu::UpdateInfo()
   {
   //Wait for an event
   while ( MthrWaitMultiple(m_MilInfoEvents, NUM_EVENTS, M_EVENT_WAIT, M_NULL) != (MIL_INT)enKill )
      {
      while (m_UpdateInfoStarted)
         {
         //Update total frames per second
         UpdateTotalFPS();

         //Update frames per second for the currently selected thread
         if (m_Processing[m_CurrentProcessing]->GetFrameRate()>0)
            UpdateCurProcFPS();

         MosSleep(UPDATE_INFO_TIME_DELAY);
         }

      //Signal that the information is no longer being updated
      MthrControl(m_MilUpdateInfoStoppedEvent, M_EVENT_SET, M_SIGNALED);
      }
   }

//*******************************************************************************
// UpdateTotalFPS.  Update Total FPS on the menu
//*******************************************************************************
void CMPMenu::UpdateTotalFPS()
   {
   MIL_DOUBLE FrameRate = 0;
   
   //Get the sum of frames per second of all threads
   for (MIL_INT i=0; i<m_CurrentProcessingNum; i++)
      {
      if (m_Processing[i]->IsRunning())
         FrameRate += m_Processing[i]->GetFrameRate();
      }

   //Update total FPS
   MosSprintf(m_InfoString, STRING_SIZE, MIL_TEXT("%.1f Total Frames per Second"), FrameRate); 
   MdispControl(m_MilMenuDisplay, M_UPDATE, M_DISABLE);
   MbufClear(m_InfoRegions[enTotalFPS].MilRectBuffer, BACKGROUND_COLOR);
   MgraText(m_MilInfoGraphicsContext, m_InfoRegions[enTotalFPS].MilRectBuffer, 0, 0, m_InfoString);
   MdispControl(m_MilMenuDisplay, M_UPDATE, M_ENABLE);
   }

//*******************************************************************************
// UpdateFPS.  Update current processing FPS on the menu
//*******************************************************************************
void CMPMenu::UpdateCurProcFPS()
   {
   //Update current processing FPS
   MosSprintf(m_InfoString, STRING_SIZE, MIL_TEXT("%.1f Frames per Second"), 
      m_Processing[m_CurrentProcessing]->GetFrameRate()); 
   MdispControl(m_MilMenuDisplay, M_UPDATE, M_DISABLE);
   MbufClear(m_InfoRegions[enCurProcFPS].MilRectBuffer, BACKGROUND_COLOR);
   MgraText(m_MilInfoGraphicsContext, m_InfoRegions[enCurProcFPS].MilRectBuffer, 0, 0, m_InfoString);
   MdispControl(m_MilMenuDisplay, M_UPDATE, M_ENABLE);
   }

//*******************************************************************************
// StartUpdateInfo.  Indicate to start updating FPS
//*******************************************************************************
void CMPMenu::StartUpdateInfo()
   {
   //Set the even to start updating frames per second
   m_UpdateInfoStarted = true;
   MthrControl(m_MilInfoEvents[enRun], M_EVENT_SET, M_SIGNALED);
   }

//*******************************************************************************
// StopUpdateInfo.  Indicate to stop updating FPS
//*******************************************************************************
void CMPMenu::StopUpdateInfo()
   {
   MIL_INT i=0;
   
   //Look for a running thread
   for (i=0; i<m_ProcessingArraySize; i++)
      {
      if (m_Processing[i]->IsRunning())
         break;
      }

   //Only stop updating the info if none of the processing are started
   if ((i==m_ProcessingArraySize) && m_UpdateInfoStarted)
      {
      m_UpdateInfoStarted = false;

      //Wait until the information on the menu has stopped updating
      MthrWait(m_MilUpdateInfoStoppedEvent, M_EVENT_WAIT, M_NULL);
      }
   }

//*****************************************************************************
// GetButtonText. Centralizes the management of the text written on buttons.
//*****************************************************************************
MIL_TEXT_CHAR* CMPMenu::GetButtonText(MIL_INT ProcessingIndex, MPButtons ButtonType,  
   MIL_TEXT_CHAR* ButtonText)
   {
   bool UseMemoryBank = false;
   MIL_INT64 CurrentMemoryBank = 0;
   MIL_UINT64 CoreMask = 0x00000001;

   //Get the text for the given button type and the currently selected user thread
   switch (ButtonType)
      {
      case enTotalThreads:
         MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%d"), m_CurrentProcessingNum);
         break;
      case enShowDisplay:
         if (m_Processing[m_CurrentProcessing]->DisplaySelected())
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Yes"));
         else
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("No"));
         break;
      case enProcThread:
         MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%d"), m_CurrentProcessing);
         break;
      case enApplyToAllThreads:
         MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Apply to All Threads"));
         break;
      case enRunProcessing:
         if (m_Processing[ProcessingIndex]->IsRunning())
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Yes"));
         else
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("No"));
         break;
      case enRunDisplay: 
         if (m_Processing[ProcessingIndex]->DisplayRunning())
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Yes"));
         else
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("No"));
         break;
      case enMemoryBank:
         CurrentMemoryBank = m_Processing[m_CurrentProcessing]->GetCurrentMemoryBank();

         if (m_Processing[m_CurrentProcessing]->UseMemoryBank())
            {
            switch(CurrentMemoryBank)
               {
               case M_MEMORY_BANK_0:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 0"));
                  break;
               case M_MEMORY_BANK_1:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 1"));
                  break;
               case M_MEMORY_BANK_2:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 2"));
                  break;
               case M_MEMORY_BANK_3:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 3"));
                  break;
               case M_MEMORY_BANK_4:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 4"));
                  break;
               case M_MEMORY_BANK_5:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 5"));
                  break;
               case M_MEMORY_BANK_6:
                  MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Bank 6"));
                  break;
               default:
                  break;
               }
            }
         else
            {
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("No Affinity"));
            }
         break;
      case enMP:  
         if (m_Processing[ProcessingIndex]->MPEnabled())
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Yes"));
         else
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("No"));
         break;
      case enCoreMax: 
         MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%d"), m_Processing[m_CurrentProcessing]->GetCoreMax());
         break;
      case enCoreSharing: 
         if (m_Processing[ProcessingIndex]->CoreSharingEnabled())
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Enabled"));
         else
            MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%s"), MIL_TEXT("Disabled"));
         break;
      case enMPPriority:
         switch (m_Processing[ProcessingIndex]->GetMPPriority())
            {
            case M_ABOVE_NORMAL:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Above Normal"));
               break;
            case M_BELOW_NORMAL:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Below Normal"));
               break;
            case M_HIGHEST:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Highest"));
               break;
            case M_IDLE:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Idle"));
               break;
            case M_LOWEST:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Lowest"));
               break;
            case M_NORMAL:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Normal"));
               break;
            case M_TIME_CRITICAL:
               MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("Time Critical"));
               break;
            default:
               break;
            }
         break;
      case enCore0:case enCore1:case enCore2:case enCore3:
      case enCore4:case enCore5:case enCore6:case enCore7:
      case enCore8:case enCore9:case enCore10:case enCore11:
      case enCore12:case enCore13:case enCore14:case enCore15:
      case enCore16:case enCore17:case enCore18:case enCore19:
      case enCore20:case enCore21:case enCore22:case enCore23:
      case enCore24:case enCore25:case enCore26:case enCore27:
      case enCore28:case enCore29:case enCore30:case enCore31:
         MosSprintf(ButtonText, STRING_SIZE, MIL_TEXT("%d, %d"), GetRealCoreIndex(ButtonType-FIRST_CORE), 
            MappInquireMp(M_DEFAULT, M_CORE_MEMORY_BANK, M_DEFAULT, GetRealCoreIndex(ButtonType-FIRST_CORE), M_NULL));
         break;
      default:
         break;
      }
   return ButtonText;
   }

//*****************************************************************************
// InitProcessingMP. Initializes the state of MP of the given processing
// object.
//*****************************************************************************
void CMPMenu::InitProcessingMP(MIL_INT NumCores, CMPProcessing* Processing)
   {
   //Set the initial MP state of the given processing object
   Processing->SetMP(true);
   Processing->SetCoreMax(NumCores);
   Processing->SetCoreSharing(false);
   Processing->SetMPPriority(M_NORMAL);
   Processing->SetCoreAffinity(0x00000000);
   Processing->SetCurrentMemoryBank(0, false);
   }


//*****************************************************************************
// GetRealCoreIndex. Get the real (physical) index of the core specified by
// the rank.  If, for example, a core affinity has been set on the process the 
// physical core indices can be different.
//*****************************************************************************
MIL_INT CMPMenu::GetRealCoreIndex(MIL_INT Rank)
   {
   MIL_INT i=0;
   MIL_INT AvailableCoresCount = 0;

   for (i=0; i<MAX_MP_CORES; i++)
      {
      if (m_CoreAffinityMaskProcess[0]&((MIL_UINT64)1<<i))
         AvailableCoresCount++;

      if (AvailableCoresCount==Rank+1)
         break;
      }

   return i;
   }

//*****************************************************************************
// MenuHookFct. Manages the interaction with the menu.
//*****************************************************************************
MIL_INT MFTYPE CMPMenu::MenuHookFct(MIL_INT HookType, MIL_ID MilEvent, void *UserDataPtr)
   {
   CMPMenu* MenuPtr = (CMPMenu*)UserDataPtr;
   MIL_INT PositionX = 0;
   MIL_INT PositionY = 0;
   MIL_UINT64 CoreMask = 0x00000001;

   //Get the position of the button click
   MdispGetHookInfo(MilEvent, M_MOUSE_POSITION_X, &PositionX);
   MdispGetHookInfo(MilEvent, M_MOUSE_POSITION_Y, &PositionY);

   //Get the button type
   MPButtons ButtonType = MenuPtr->GetButton(PositionX, PositionY);

   //Call the click handler function according to the button type
   switch (ButtonType)
      {
      case enTotalThreadsLeft:
         MenuPtr->NumberOfThreadsButtonClick(false);
         break;
      case enTotalThreadsRight:
         MenuPtr->NumberOfThreadsButtonClick(true);
         break;
      case enShowDisplay:
         MenuPtr->AllThreadsDisplayButtonClick();
         break;
      case enProcThreadLeft:
         MenuPtr->ProcessingThreadButtonClick(false);
         break;
      case enProcThreadRight:
         MenuPtr->ProcessingThreadButtonClick(true);
         break;
      case enApplyToAllThreads:
         MenuPtr->ApplyToAllThreadsButtonClick();
         break;
      case enRunProcessing:
         MenuPtr->ProcessingButtonClick();
         break;
      case enRunDisplay:
         MenuPtr->CurrentThreadDisplayButtonClick();
         break;
      case enMemoryBankLeft:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->MemoryBankButtonClick(false);
         break;
      case enMemoryBankRight:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->MemoryBankButtonClick(true);
         break;
      case enMP:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->MPButtonClick();
         break;
      case enCoreMaxLeft:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->CoreMaxButtonClick(false);
         break;
      case enCoreMaxRight:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->CoreMaxButtonClick(true);
         break;
      case enCoreSharing:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->CoreSharingButtonClick();
         break;
      case enMPPriorityLeft:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->MPPriorityButtonClick(false);
         break;
      case enMPPriorityRight:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->MPPriorityButtonClick(true);
         break;
      //Core affinity handling
      case enCore0:case enCore1:case enCore2:case enCore3:
      case enCore4:case enCore5:case enCore6:case enCore7:
      case enCore8:case enCore9:case enCore10:case enCore11:
      case enCore12:case enCore13:case enCore14:case enCore15:
      case enCore16:case enCore17:case enCore18:case enCore19:
      case enCore20:case enCore21:case enCore22:case enCore23:
      case enCore24:case enCore25:case enCore26:case enCore27:
      case enCore28:case enCore29:case enCore30:case enCore31:
         if (MenuPtr->m_NumberOfProcessorCores>=MIN_MP_CORES)
            MenuPtr->CoreAffinityButtonClick(ButtonType-FIRST_CORE);
         break;
      default:
         break;
      };

   return 0;
   }

//*****************************************************************************
// UpdateInfoFunction. Function that updates FPS
//*****************************************************************************
MIL_UINT32 MFTYPE CMPMenu::UpdateInfoFunction(void* DataPtr)
   {
   CMPMenu* Menu = (CMPMenu*)DataPtr;

   Menu->UpdateInfo();
   return 0;
   }