/* *-------------------------------------------------------------------- * Project: VM65 - Virtual Machine/CPU emulator programming * framework. * * File: GraphDisp.cpp * * Purpose: Implementation of GraphDisp class. * The GraphDisp class emulates the graphics raster * device. It handles displaying of the graphics, the * events loop and the 'hardware' API to the device. * The higher level abstraction layer - MemMapDev * defines the actual behavior/response of the emulated * device in the emulated system. * * Date: 8/25/2016 * * Copyright: (C) by Marek Karcz 2016. All rights reserved. * * Contact: makarcz@yahoo.com * * License Agreement and Warranty: This software is provided with No Warranty. I (Marek Karcz) will not be held responsible for any damage to computer systems, data or user's health resulting from use. Please proceed responsibly and apply common sense. This software is provided in hope that it will be useful. It is free of charge for non-commercial and educational use. Distribution of this software in non-commercial and educational derivative work is permitted under condition that original copyright notices and comments are preserved. Some 3-rd party work included with this project may require separate application for permission from their respective authors/copyright owners. *-------------------------------------------------------------------- */ #include "GraphDisp.h" #include "MKGenException.h" #include "system.h" #if defined(WINDOWS) #include "wtypes.h" #endif namespace MKBasic { /* *-------------------------------------------------------------------- * Method: GraphDisp() * Purpose: Class default constructor. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ GraphDisp::GraphDisp() { Initialize(); } /* *-------------------------------------------------------------------- * Method: GraphDisp() * Purpose: Class custom constructor. * Arguments: width, height - integer, graphical display dimensions * (virtual dimensions as opposed to * to actual dimensions GRAPHDISP_MAXW, * GRAPHDISP_MAXH) * Returns: n/a *-------------------------------------------------------------------- */ GraphDisp::GraphDisp(int width, int height) { mWidth = width; mHeight = height; Initialize(); } /* *-------------------------------------------------------------------- * Method: ~GraphDisp() * Purpose: Class destructor. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ GraphDisp::~GraphDisp() { mContLoop = false; while (mMainLoopActive); SDL_DestroyRenderer(mpRenderer); SDL_DestroyWindow(mpWindow); SDL_Quit(); } /* *-------------------------------------------------------------------- * Method: Initialize() * Purpose: Initialize class members, objects. * Arguments: n/a * Returns: n/a * Problems: * SDL_CreateWindow with flags: * SDL_WINDOW_SHOWN|SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE * creates window with no title/icon/system buttons, but with * a frame, which can be used to resize it. The problem is, when * it is resized, it stops updating correctly. *-------------------------------------------------------------------- */ void GraphDisp::Initialize() { int desk_w, desk_h, winbd_top = 5, winbd_right = 5; mContLoop = true; mMainLoopActive = false; mWidth = GRDISP_VR_X; // virtual display width mHeight = GRDISP_VR_Y; // virtual display height mPixelSizeX = GRAPHDISP_MAXW / mWidth; mPixelSizeY = GRAPHDISP_MAXH / mHeight; mWinPosX = 0; // SDL window position coordinate X mWinPosY = 0; // SDL window position coordinate Y mBgRgbR = 0; // bg color, RGB red intensity mBgRgbG = 0; // bg color, RGB green intensity mBgRgbB = 0; // bg color, RGB blue intensity mFgRgbR = 0xFF; // fg color, RGB red intensity mFgRgbG = 0xFF; // fg color, RGB green intensity mFgRgbB = 0xFF; // fg color, RGB blue intensity mpWindow = NULL; mpSurface = NULL; mpRenderer = NULL; GetDesktopResolution(desk_w, desk_h); // Available in version > 2.0.4 //SDL_GetWindowBordersSize(mpWindow, &winbd_top, NULL, NULL, &winbd_right); mWinPosX = desk_w - GRAPHDISP_MAXW - winbd_right; mWinPosY = winbd_top; SDL_Init(SDL_INIT_VIDEO); mpWindow = SDL_CreateWindow( "GraphDisp", mWinPosX, mWinPosY, GRAPHDISP_MAXW, GRAPHDISP_MAXH, SDL_WINDOW_SHOWN|SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE ); if (NULL == mpWindow) { throw MKGenException(SDL_GetError()); } // Get window surface mpSurface = SDL_GetWindowSurface(mpWindow); // Create renderer for window mpRenderer = SDL_CreateRenderer(mpWindow, -1, SDL_RENDERER_SOFTWARE); //mpRenderer = SDL_GetRenderer(mpWindow); if (NULL == mpRenderer) { throw MKGenException(SDL_GetError()); } Clear(); } /* *-------------------------------------------------------------------- * Method: ClearScreen() * Purpose: Clear the surface. Update screen. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::ClearScreen() { Clear(); UpdateSurface(); } /* *-------------------------------------------------------------------- * Method: Clear() * Purpose: Clear the surface. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::Clear() { //Fill the surface with background color SDL_FillRect(mpSurface, NULL, SDL_MapRGB(mpSurface->format, mBgRgbR, mBgRgbG, mBgRgbB)); } /* *-------------------------------------------------------------------- * Method: UpdateSurface() * Purpose: Update window surface. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::UpdateSurface() { //Update the surface SDL_UpdateWindowSurface(mpWindow); } /* *-------------------------------------------------------------------- * Method: Update() * Purpose: Update window surface (public). * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::Update() { //Update the surface UpdateSurface(); } /* *-------------------------------------------------------------------- * Method: RenderPixel() * Purpose: Set or unset pixel (scaled) on graphics display * surface. * Arguments: x, y - integer, virtual pixel coordinates * set - boolean, set or unset pixel * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::RenderPixel(int x, int y, bool set) { SDL_Rect rtf; rtf.x = x * mPixelSizeX; rtf.y = y * mPixelSizeY; rtf.w = mPixelSizeX; rtf.h = mPixelSizeY; int rgb_r = 0, rgb_g = 0, rgb_b = 0; if (set) { rgb_r = mFgRgbR; rgb_g = mFgRgbG; rgb_b = mFgRgbB; } else { rgb_r = mBgRgbR; rgb_g = mBgRgbG; rgb_b = mBgRgbB; } // set or unset pixel SDL_FillRect(mpSurface, &rtf, SDL_MapRGB(mpSurface->format, rgb_r, rgb_g, rgb_b)); //Update the surface SDL_UpdateWindowSurface(mpWindow); } /* *-------------------------------------------------------------------- * Method: RendedChar8x8() * Purpose: Draw 8x8 character from its pixel definition. * Arguments: chdef - character definition in 8x8 bit matrix * x, y - coordinates * reversed - reversed (true) or normal (false) * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::RenderChar8x8(unsigned char chdef[8], int x, int y, bool reversed) { SDL_Rect rtf; int rgb_r = 0, rgb_g = 0, rgb_b = 0; for (int yy = y, j=0; j < 8; j++, yy++) { unsigned char chd = chdef[j]; for (int xx = x, i=0; i < 8; i++, xx++) { bool pixset = (chd & 0x80) == 0x80; if (reversed) pixset = !pixset; rtf.x = xx * mPixelSizeX; rtf.y = yy * mPixelSizeY; rtf.w = mPixelSizeX; rtf.h = mPixelSizeY; if (pixset) { rgb_r = mFgRgbR; rgb_g = mFgRgbG; rgb_b = mFgRgbB; } else { rgb_r = mBgRgbR; rgb_g = mBgRgbG; rgb_b = mBgRgbB; } SDL_FillRect(mpSurface, &rtf, SDL_MapRGB(mpSurface->format, rgb_r, rgb_g, rgb_b)); chd = chd << 1; chd &= 0xFE; } } SDL_UpdateWindowSurface(mpWindow); } /* *-------------------------------------------------------------------- * Method: CopyCharRom8x8() * Purpose: Copy provided 8x8 characters table to internal buffer. * Arguments: pchrom - pointer to characters defintions table * Returns: *-------------------------------------------------------------------- */ void GraphDisp::CopyCharRom8x8(unsigned char *pchrom) { for (int i=0; imMainLoopActive = true; while (pgd->mContLoop) { pgd->ReadEvents(); pgd->Update(); } pgd->mMainLoopActive = false; } /* *-------------------------------------------------------------------- * Method: Start() * Purpose: Starts MainLoop in a thread. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::Start(GraphDisp *pgd) { mMainLoopThread = thread(MainLoop,pgd); } /* *-------------------------------------------------------------------- * Method: Stop() * Purpose: Stop MainLoop thread. * Arguments: n/a * Returns: n/a *-------------------------------------------------------------------- */ void GraphDisp::Stop() { mContLoop = false; mMainLoopThread.join(); } /* *-------------------------------------------------------------------- * Method: * Purpose: * Arguments: * Returns: *-------------------------------------------------------------------- */ bool GraphDisp::IsMainLoopActive() { return mMainLoopActive; } /* *-------------------------------------------------------------------- * Method: ReadEvents() * Purpose: Read events and perform actions when needed. * Arguments: n/a * Returns: n/a * Problems: * By blitting (copying) surface I wanted to avoid loosing already * drawn pixels during windows resize, but this doesn't work. *-------------------------------------------------------------------- */ void GraphDisp::ReadEvents() { SDL_Event e; SDL_Surface *pTmpSurface; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: mContLoop = false; break; case SDL_WINDOWEVENT: if (SDL_WINDOWEVENT_RESIZED == e.window.event) { pTmpSurface = SDL_CreateRGBSurface(0, mpSurface->w, mpSurface->h, mpSurface->format->BitsPerPixel, mpSurface->format->Rmask, mpSurface->format->Gmask, mpSurface->format->Bmask, mpSurface->format->Amask); SDL_SetWindowSize(mpWindow, GRAPHDISP_MAXW, GRAPHDISP_MAXH); mpSurface = SDL_GetWindowSurface(mpWindow); SDL_SetWindowPosition(mpWindow, mWinPosX, mWinPosY); SDL_SetSurfaceAlphaMod(pTmpSurface, 0); SDL_BlitSurface(pTmpSurface, 0, mpSurface, 0); UpdateSurface(); SDL_FreeSurface(pTmpSurface); } else if (SDL_WINDOWEVENT_FOCUS_GAINED == e.window.event) { SDL_RaiseWindow(mpWindow); } break; default: break; } } } /* *-------------------------------------------------------------------- * Method: * Purpose: * Arguments: * Returns: *-------------------------------------------------------------------- */ void GraphDisp::GetDesktopResolution(int& horizontal, int& vertical) { #if defined(WINDOWS) RECT desktop; // Get a handle to the desktop window const HWND hDesktop = GetDesktopWindow(); // Get the size of screen to the variable desktop GetWindowRect(hDesktop, &desktop); // The top left corner will have coordinates (0,0) // and the bottom right corner will have coordinates // (horizontal, vertical) horizontal = desktop.right; vertical = desktop.bottom; #else horizontal = GRAPHDISP_MAXW; vertical = GRAPHDISP_MAXH; #endif } } // namespace MKBasic