mirror of https://github.com/makarcz/vm6502.git
599 lines
16 KiB
C++
599 lines
16 KiB
C++
/*
|
|
*--------------------------------------------------------------------
|
|
* 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; i<CHROM_8x8_SIZE; i++) {
|
|
mCharROM8x8[i] = pchrom[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: PrintChar8x8()
|
|
* Purpose: Print 8x8 character at specified row and column.
|
|
* Arguments: code - character code
|
|
* col, row - character coordinates in 8 pixel intervals
|
|
* reversed - color mode (reversed or nornal)
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::PrintChar8x8(int code, int col, int row, bool reversed)
|
|
{
|
|
int x = col * 8;
|
|
int y = row * 8;
|
|
int n = code * 8;
|
|
unsigned char chdef[8];
|
|
for (int i=0; i<8; i++) {
|
|
chdef[i] = mCharROM8x8[n+i];
|
|
}
|
|
RenderChar8x8(chdef, x, y, reversed);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetPixel()
|
|
* Purpose: Set pixel (scaled) on graphics display surface.
|
|
* Arguments: x, y - pixel coordinates
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::SetPixel(int x, int y)
|
|
{
|
|
RenderPixel(x, y, true);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: ErasePixel()
|
|
* Purpose: Unset (erase) pixel (paint with BG color) on graphics
|
|
* display surface.
|
|
* Arguments: x, y - pixel coordinates
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::ErasePixel(int x, int y)
|
|
{
|
|
RenderPixel(x, y, false);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DrawLine()
|
|
* Purpose: Draw or erase line between specified points.
|
|
* Arguments: x1, y1 - coordinates of first point
|
|
* x2, y2 - coordinates of second point
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::DrawLine(int x1, int y1, int x2, int y2, bool draworerase)
|
|
{
|
|
int colR = mFgRgbR, colG = mFgRgbG, colB = mFgRgbB;
|
|
|
|
if (false == draworerase) {
|
|
colR = mBgRgbR;
|
|
colG = mBgRgbG;
|
|
colB = mBgRgbB;
|
|
}
|
|
|
|
SDL_SetRenderDrawColor(mpRenderer, colR, colG, colB, SDL_ALPHA_OPAQUE);
|
|
SDL_RenderSetLogicalSize(mpRenderer, mWidth, mHeight);
|
|
SDL_RenderSetScale(mpRenderer, mPixelSizeX, mPixelSizeY);
|
|
SDL_RenderDrawLine(mpRenderer, x1, y1, x2, y2);
|
|
SDL_RenderPresent(mpRenderer);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: DrawLine()
|
|
* Purpose: Draw line between specified points.
|
|
* Arguments: x1, y1 - coordinates of first point
|
|
* x2, y2 - coordinates of second point
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::DrawLine(int x1, int y1, int x2, int y2)
|
|
{
|
|
DrawLine(x1, y1, x2, y2, true);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: EraseLine()
|
|
* Purpose: Erase line between specified points (draw with BG color)
|
|
* Arguments: x1, y1 - coordinates of first point
|
|
* x2, y2 - coordinates of second point
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::EraseLine(int x1, int y1, int x2, int y2)
|
|
{
|
|
DrawLine(x1, y1, x2, y2, false);
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetBgColor()
|
|
* Purpose: Set background color.
|
|
* Arguments: r, g, b - integer, red, green and blue intensities
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::SetBgColor(int r, int g, int b)
|
|
{
|
|
mBgRgbR = r;
|
|
mBgRgbG = g;
|
|
mBgRgbB = b;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: SetFgColor()
|
|
* Purpose: Set foreground color.
|
|
* Arguments: r, g, b - integer, red, green and blue intensities
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void GraphDisp::SetFgColor(int r, int g, int b)
|
|
{
|
|
mFgRgbR = r;
|
|
mFgRgbG = g;
|
|
mFgRgbB = b;
|
|
}
|
|
|
|
/*
|
|
*--------------------------------------------------------------------
|
|
* Method: MainLoop()
|
|
* Purpose: The main loop to process SDL events and update window.
|
|
* This is a global function meant to run in a thread.
|
|
* Arguments: n/a
|
|
* Returns: n/a
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
void MainLoop(GraphDisp *pgd)
|
|
{
|
|
pgd->mMainLoopActive = 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
|