Added a custom version of QPainter which seems to perform much better 20%->11% CPU.

It only works if all images involved have the same format.
Using some typedefs, one can revert to the old QPixmap behaviour.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2019-11-16 11:59:37 +00:00
parent 54d418725e
commit 4d0af5d3ca
11 changed files with 245 additions and 110 deletions

View file

@ -12,7 +12,6 @@ add_executable(qapple
preferences.cpp
emulator.cpp
memorycontainer.cpp
graphicscache.cpp
gamepadpaddle.cpp
video.cpp
settings.cpp
@ -20,6 +19,8 @@ add_executable(qapple
audiogenerator.cpp
loggingcategory.cpp
graphics/graphicscache.cpp
qhexedit2/commands.cpp
qhexedit2/chunks.cpp
qhexedit2/qhexedit.cpp

View file

@ -20,9 +20,9 @@ void Emulator::repaintVideo()
video->repaint();
}
const QPixmap & Emulator::getScreen() const
bool Emulator::saveScreen(const QString & filename) const
{
return video->getScreen();
return video->getScreen().save(filename);
}
void Emulator::setVideoSize(QMdiSubWindow * window, const QSize & size)

View file

@ -10,12 +10,12 @@ class Emulator : public QFrame, private Ui::Emulator
Q_OBJECT
public:
explicit Emulator(QWidget *parent = 0);
explicit Emulator(QWidget *parent = nullptr);
void updateVideo();
void repaintVideo();
const QPixmap & getScreen() const;
bool saveScreen(const QString & filename) const;
void setZoom(QMdiSubWindow * window, const int x);
void set43AspectRatio(QMdiSubWindow * window);

View file

@ -1,11 +1,11 @@
#include "graphicscache.h"
#include "graphics/graphicscache.h"
#include <QPainter>
namespace
{
void halfScanLines(QPixmap & charset, const QColor & black)
void halfScanLines(QPaintDevice & charset, const QColor & black)
{
const int height = charset.height();
const int width = charset.width();
@ -13,18 +13,18 @@ namespace
QPainter paint(&charset);
paint.setPen(black);
for (int i = 0; i < height; i += 2)
for (int i = 1; i < height; i += 2)
{
paint.drawLine(0, i, width - 1, i);
}
}
QPixmap buildHiResMono80()
GraphicsCache::Image_t buildHiResMono80()
{
const QColor black(Qt::black);
const QColor white(Qt::white);
QPixmap hires(7, 128 * 2); // 128 values 2 lines each
GraphicsCache::Image_t hires(GraphicsCache::ScreenPainter_t::create(7, 128 * 2)); // 128 values 2 lines each
QPainter painter(&hires);
@ -59,9 +59,9 @@ namespace
WHITE
};
#define SETFRAMECOLOR(c, r, g, b) colors[c].setRgb(r, g, b);
#define SETFRAMECOLOR(c, r, g, b) colors[c].setRgb(r, g, b)
QPixmap buildLoResColor80()
GraphicsCache::Image_t buildLoResColor80()
{
std::vector<QColor> colors(16);
@ -86,7 +86,7 @@ namespace
SETFRAMECOLOR(WHITE, 0xFF,0xFF,0xFF); // FF
QPixmap lores(7, 256 * 16); // 256 values 16 lines each
GraphicsCache::Image_t lores(GraphicsCache::ScreenPainter_t::create(7, 256 * 16)); // 128 values 2 lines each
QPainter painter(&lores);
@ -101,15 +101,15 @@ namespace
return lores;
}
}
GraphicsCache::GraphicsCache()
{
const QColor black(Qt::black);
myCharset40.load(":/resources/CHARSET4.BMP");
GraphicsCache::ScreenPainter_t::convert(myCharset40);
halfScanLines(myCharset40, black);
myCharset80 = myCharset40.scaled(myCharset40.width() / 2, myCharset40.height());
@ -127,3 +127,10 @@ GraphicsCache::GraphicsCache()
myLoResColor40 = myLoResColor80.scaled(myLoResColor80.width() * 2, myLoResColor80.height());
// it was already half scan
}
GraphicsCache::Image_t GraphicsCache::createBlackScreenImage()
{
GraphicsCache::Image_t screen = GraphicsCache::ScreenPainter_t::create(40 * 7 * 2, 24 * 8 * 2);
screen.fill(Qt::black);
return screen;
}

View file

@ -0,0 +1,60 @@
#ifndef GRAPHICSCACHE_H
#define GRAPHICSCACHE_H
#include <QPixmap>
#include <QImage>
#include "graphics/painters.h"
class GraphicsCache
{
public:
GraphicsCache();
typedef FastPainter ScreenPainter_t;
typedef ScreenPainter_t::Source_t Image_t;
static Image_t createBlackScreenImage();
const Image_t & text40Col() const
{
return myCharset40;
}
const Image_t & text80Col() const
{
return myCharset80;
}
const Image_t & hires40() const
{
return myHiResMono40;
}
const Image_t & hires80() const
{
return myHiResMono80;
}
const Image_t & lores40() const
{
return myLoResColor40;
}
const Image_t & lores80() const
{
return myLoResColor80;
}
private:
Image_t myCharset40;
Image_t myCharset80;
Image_t myHiResMono40;
Image_t myHiResMono80;
Image_t myLoResColor40;
Image_t myLoResColor80;
};
#endif // GRAPHICSCACHE_H

View file

@ -0,0 +1,115 @@
#ifndef PAINTERS_H
#define PAINTERS_H
#include <QImage>
#include <QPixmap>
#include <QPainter>
inline void drawAny(QPainter & painter, const QPixmap & image)
{
painter.drawPixmap(0, 0, image);
}
inline void drawAny(QPainter & painter, const QImage & image)
{
painter.drawImage(0, 0, image);
}
inline QPixmap toPixmap(const QPixmap & image)
{
return image;
}
inline QPixmap toPixmap(const QImage & image)
{
return QPixmap::fromImage(image);
}
class FastPainter
{
public:
typedef QImage Source_t;
FastPainter(QImage * image)
: myImage(image)
{
myBits = myImage->bits();
myBytesPerLine = myImage->bytesPerLine();
const QPixelFormat format = myImage->pixelFormat();
myBytesPerPixel = format.bitsPerPixel() / 8;
}
void draw(int x, int y, const QImage & source, int sx, int sy, int wx, int wy)
{
// this only works if the 2 images have the same format
const int destOffset = x * myBytesPerPixel;
const int sourceOffset = sx * myBytesPerPixel;
const int length = wx * myBytesPerPixel;
const int sourceBytesPerLine = source.bytesPerLine();
const uchar * sourceBits = source.bits();
for (int iy = 0; iy < wy; iy += 1)
{
const uchar * sourceLine = sourceBits + sourceBytesPerLine * (sy + iy);
uchar * destLine = myBits + myBytesPerLine * (y + iy);
memcpy(destLine + destOffset, sourceLine + sourceOffset, length);
}
}
static QImage create(int width, int height)
{
return QImage(width, height, theFormat);
}
static void convert(QImage & image)
{
image = image.convertToFormat(theFormat);
}
private:
static const QImage::Format theFormat = QImage::Format_RGB32;
QImage * myImage;
uchar * myBits;
int myBytesPerPixel;
int myBytesPerLine;
};
class Painter : QPainter
{
public:
typedef QPixmap Source_t;
//typedef QImage Source_t;
Painter(QPaintDevice * image)
: QPainter(image)
{
}
void draw(int x, int y, const QImage & source, int sx, int sy, int wx, int wy)
{
drawImage(x, y, source, sx, sy, wx, wy);
}
void draw(int x, int y, const QPixmap & source, int sx, int sy, int wx, int wy)
{
drawPixmap(x, y, source, sx, sy, wx, wy);
}
static QPixmap create(int width, int height)
{
return QPixmap(width, height);
}
static void convert(Source_t & image)
{
Q_UNUSED(image)
}
};
#endif // PAINTERS_H

View file

@ -1,53 +0,0 @@
#ifndef GRAPHICSCACHE_H
#define GRAPHICSCACHE_H
#include <QPixmap>
class GraphicsCache
{
public:
GraphicsCache();
const QPixmap & text40Col() const
{
return myCharset40;
}
const QPixmap & text80Col() const
{
return myCharset80;
}
const QPixmap & hires40() const
{
return myHiResMono40;
}
const QPixmap & hires80() const
{
return myHiResMono80;
}
const QPixmap & lores40() const
{
return myLoResColor40;
}
const QPixmap & lores80() const
{
return myLoResColor80;
}
private:
QPixmap myCharset40;
QPixmap myCharset80;
QPixmap myHiResMono40;
QPixmap myHiResMono80;
QPixmap myLoResColor40;
QPixmap myLoResColor80;
};
#endif // GRAPHICSCACHE_H

View file

@ -442,7 +442,7 @@ void QApple::on_actionScreenshot_triggered()
}
else
{
const bool ok = myEmulator->getScreen().save(filename);
const bool ok = myEmulator->saveScreen(filename);
if (!ok)
{
const QString message = QString::fromUtf8("Cannot save screenshot to %1").arg(filename);

View file

@ -12,12 +12,12 @@ TEMPLATE = app
SOURCES += main.cpp\
audiogenerator.cpp \
graphics/graphicscache.cpp \
loggingcategory.cpp \
qapple.cpp \
qresources.cpp \
emulator.cpp \
video.cpp \
graphicscache.cpp \
memorycontainer.cpp \
preferences.cpp \
gamepadpaddle.cpp \
@ -30,6 +30,8 @@ SOURCES += main.cpp\
HEADERS += qapple.h \
audiogenerator.h \
emulator.h \
graphics/graphicscache.h \
graphics/painters.h \
loggingcategory.h \
video.h \
graphicscache.h \

View file

@ -3,7 +3,7 @@
#include <QPainter>
#include <QKeyEvent>
#include "graphicscache.h"
#include "graphics/graphicscache.h"
#include "StdAfx.h"
#include "Video.h"
@ -34,15 +34,14 @@ namespace
{
return ((x << 1) & 0x0F) | ((x >> 3) & 0x01);
}
}
bool Video::Update40ColCell (QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::Update40ColCell (ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const QPixmap & text40Col = myGraphicsCache->text40Col();
const GraphicsCache::Image_t & text40Col = myGraphicsCache->text40Col();
const BYTE ch = *(g_pTextBank0 + offset);
@ -54,17 +53,17 @@ bool Video::Update40ColCell (QPainter & painter, int x, int y, int xpixel, int y
const int sx = 16 * column;
const int sy = 16 * (base + row);
painter.drawPixmap(xpixel, ypixel, text40Col, sx, sy, 14, 16);
painter.draw(xpixel, ypixel, text40Col, sx, sy, 14, 16);
return true;
}
bool Video::Update80ColCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::Update80ColCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const QPixmap & text80Col = myGraphicsCache->text80Col();
const GraphicsCache::Image_t & text80Col = myGraphicsCache->text80Col();
const int base = g_nAltCharSetOffset ? 16 : 0;
@ -76,7 +75,7 @@ bool Video::Update80ColCell(QPainter & painter, int x, int y, int xpixel, int yp
const int sx = 8 * column;
const int sy = 16 * (row + base);
painter.drawPixmap(xpixel, ypixel, text80Col, sx, sy, 7, 16);
painter.draw(xpixel, ypixel, text80Col, sx, sy, 7, 16);
}
{
@ -87,35 +86,35 @@ bool Video::Update80ColCell(QPainter & painter, int x, int y, int xpixel, int yp
const int sx = 8 * column;
const int sy = 16 * (row + base);
painter.drawPixmap(xpixel + 7, ypixel, text80Col, sx, sy, 7, 16);
painter.draw(xpixel + 7, ypixel, text80Col, sx, sy, 7, 16);
}
return true;
}
bool Video::UpdateLoResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::UpdateLoResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const QPixmap & lores = myGraphicsCache->lores40();
const GraphicsCache::Image_t & lores = myGraphicsCache->lores40();
const BYTE ch = *(g_pTextBank0 + offset);
const int sx = 0;
const int sy = ch * 16;
painter.drawPixmap(xpixel, ypixel, lores, sx, sy, 14, 16);
painter.draw(xpixel, ypixel, lores, sx, sy, 14, 16);
return true;
}
bool Video::UpdateDLoResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::UpdateDLoResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const QPixmap & lores = myGraphicsCache->lores80();
const GraphicsCache::Image_t & lores = myGraphicsCache->lores80();
{
BYTE ch = *(g_pTextBank1 + offset);
@ -127,7 +126,7 @@ bool Video::UpdateDLoResCell(QPainter & painter, int x, int y, int xpixel, int y
const int sx = 0;
const int sy = ch * 16;
painter.drawPixmap(xpixel, ypixel, lores, sx, sy, 7, 16);
painter.draw(xpixel, ypixel, lores, sx, sy, 7, 16);
}
{
@ -136,19 +135,19 @@ bool Video::UpdateDLoResCell(QPainter & painter, int x, int y, int xpixel, int y
const int sx = 0;
const int sy = ch * 16;
painter.drawPixmap(xpixel + 7, ypixel, lores, sx, sy, 7, 16);
painter.draw(xpixel + 7, ypixel, lores, sx, sy, 7, 16);
}
return true;
}
bool Video::UpdateHiResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::UpdateHiResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const BYTE * base = g_pHiresBank0 + offset;
const QPixmap & hires = myGraphicsCache->hires40();
const GraphicsCache::Image_t & hires = myGraphicsCache->hires40();
for (size_t i = 0; i < 8; ++i)
{
@ -157,18 +156,18 @@ bool Video::UpdateHiResCell(QPainter & painter, int x, int y, int xpixel, int yp
const int row = value & 0x7f;
painter.drawPixmap(xpixel, ypixel + i * 2, hires, 0, row * 2, 14, 2);
painter.draw(xpixel, ypixel + i * 2, hires, 0, row * 2, 14, 2);
}
return true;
}
bool Video::UpdateDHiResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset)
bool Video::UpdateDHiResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset)
{
Q_UNUSED(x)
Q_UNUSED(y)
const QPixmap & dhires = myGraphicsCache->hires80();
const GraphicsCache::Image_t & dhires = myGraphicsCache->hires80();
{
const BYTE * base = g_pHiresBank1 + offset;
@ -180,7 +179,7 @@ bool Video::UpdateDHiResCell(QPainter & painter, int x, int y, int xpixel, int y
const int row = value & 0x7f;
painter.drawPixmap(xpixel, ypixel + i * 2, dhires, 0, row * 2, 7, 2);
painter.draw(xpixel, ypixel + i * 2, dhires, 0, row * 2, 7, 2);
}
}
@ -194,7 +193,7 @@ bool Video::UpdateDHiResCell(QPainter & painter, int x, int y, int xpixel, int y
const int row = value & 0x7f;
painter.drawPixmap(xpixel + 7, ypixel + i * 2, dhires, 0, row * 2, 7, 2);
painter.draw(xpixel + 7, ypixel + i * 2, dhires, 0, row * 2, 7, 2);
}
}
@ -206,8 +205,7 @@ Video::Video(QWidget *parent) : VIDEO_BASECLASS(parent)
myGraphicsCache.reset(new GraphicsCache());
setMouseTracking(true);
const QSize standard(40 * 7 * 2, 24 * 8 * 2);
myOffscreen = QPixmap(standard);
myOffscreen = GraphicsCache::createBlackScreenImage();
}
void Video::paintEvent(QPaintEvent *)
@ -231,7 +229,7 @@ void Video::paintEvent(QPaintEvent *)
// first draw the image offscreen 1:1
{
QPainter painter(&myOffscreen);
ScreenPainter_t painter(&myOffscreen);
paint(painter);
}
@ -239,16 +237,16 @@ void Video::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.scale(sx, sy);
painter.drawPixmap(0, 0, myOffscreen);
drawAny(painter, myOffscreen);
}
}
const QPixmap & Video::getScreen() const
GraphicsCache::Image_t Video::getScreen() const
{
return myOffscreen;
}
void Video::paint(QPainter & painter)
void Video::paint(ScreenPainter_t & painter)
{
const int displaypage2 = (SW_PAGE2) == 0 ? 0 : 1;
@ -257,7 +255,7 @@ void Video::paint(QPainter & painter)
g_pTextBank1 = MemGetAuxPtr (0x400 << displaypage2);
g_pTextBank0 = MemGetMainPtr(0x400 << displaypage2);
typedef bool (Video::*VideoUpdateFuncPtr_t)(QPainter&,int,int,int,int,int);
typedef bool (Video::*VideoUpdateFuncPtr_t)(ScreenPainter_t&,int,int,int,int,int);
VideoUpdateFuncPtr_t update = SW_TEXT
? SW_80COL

View file

@ -4,18 +4,22 @@
#include <QOpenGLWidget>
#include <memory>
#include "graphics/graphicscache.h"
#define VIDEO_BASECLASS QOpenGLWidget
//#define VIDEO_BASECLASS QWidget
class GraphicsCache;
class Video : public VIDEO_BASECLASS
{
Q_OBJECT
public:
typedef GraphicsCache::ScreenPainter_t ScreenPainter_t;
typedef GraphicsCache::Image_t Image_t;
explicit Video(QWidget *parent = 0);
const QPixmap & getScreen() const;
Image_t getScreen() const;
signals:
@ -29,19 +33,20 @@ protected:
virtual void mouseReleaseEvent(QMouseEvent *event);
private:
bool Update40ColCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool Update80ColCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateLoResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateDLoResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateHiResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateDHiResCell(QPainter & painter, int x, int y, int xpixel, int ypixel, int offset);
bool Update40ColCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
bool Update80ColCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateLoResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateDLoResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateHiResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
bool UpdateDHiResCell(ScreenPainter_t & painter, int x, int y, int xpixel, int ypixel, int offset);
// paint the whole screen
// no scale applied
void paint(QPainter & painter);
void paint(ScreenPainter_t & painter);
std::shared_ptr<const GraphicsCache> myGraphicsCache;
QPixmap myOffscreen;
Image_t myOffscreen;
};
#endif // VIDEO_H