conversion to 16-bit blit

This commit is contained in:
Jorj Bauer 2020-07-06 16:46:14 -04:00
parent c9fe8edc29
commit 7f7d1cc5ce
7 changed files with 146 additions and 103 deletions

View File

@ -18,7 +18,7 @@ class PhysicalDisplay {
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c) = 0;
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str) = 0;
virtual void debugMsg(const char *msg) { strncpy(overlayMessage, msg, sizeof(overlayMessage)); }
virtual void debugMsg(const char *msg) { strncpy(overlayMessage, msg, sizeof(overlayMessage));overlayMessage[strlen(overlayMessage)] = 0; }
virtual void drawPixel(uint16_t x, uint16_t y, uint16_t color) = 0;
virtual void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) = 0;

View File

@ -1,8 +1,11 @@
#include <ctype.h> // isgraph
#include <DMAChannel.h>
#include "teensy-display.h"
#include "bios-font.h"
#include "appleui.h"
#include <SPI.h>
#define _clock 65000000
@ -13,14 +16,28 @@
#define PIN_MISO 12
#define PIN_SCK 13
#define disp_x_size 239
#define disp_y_size 319
// Inside the 320x240 display, the Apple display is 280x192.
// (That's half the "correct" width, b/c of double-hi-res.)
#define apple_display_w 280
#define apple_display_h 192
// Inset inside the apple2 "frame" where we draw the display
// remember these are "starts at pixel number" values, where 0 is the first.
#define HOFFSET 18
#define VOFFSET 13
#include "globals.h"
#include "applevm.h"
DMAMEM uint16_t dmaBuffer[240][320]; // 240 rows, 320 columns
#define RGBto565(r,g,b) (((r & 0x3E00) << 2) | ((g & 0x3F00) >>3) | ((b & 0x3E00) >> 9))
ILI9341_t3 tft = ILI9341_t3(PIN_CS, PIN_DC, PIN_RST, PIN_MOSI, PIN_SCK, PIN_MISO);
DMAChannel dmatx;
DMASetting dmaSetting;
// RGB map of each of the lowres colors
const uint16_t loresPixelColors[16] = { 0x0000, // 0 black
0xC006, // 1 magenta
@ -78,18 +95,40 @@ const uint16_t loresPixelColorsWhite[16] = { 0x0000,
TeensyDisplay::TeensyDisplay()
{
memset(videoBuffer, 0, sizeof(videoBuffer));
memset(dmaBuffer, 0x80, sizeof(dmaBuffer));
tft.begin();
tft.setRotation(3);
tft.setClock(_clock);
// Could set up an automatic DMA transfer here; cf.
// Set up automatic DMA transfers. cf.
// https://forum.pjrc.com/threads/25778-Could-there-be-something-like-an-ISR-template-function/page4
#if 0
dmaSetting.TCD->CSR = 0;
dmaSetting.TCD->SADDR = dmaBuffer;
dmaSetting.TCD->SOFF = 2; // 2 bytes per pixel
dmaSetting.TCD->ATTR_SRC = 1;
dmaSetting.TCD->NBYTES = 2;
dmaSetting.TCD->SLAST = -320*240*2;
dmaSetting.TCD->BITER = 320*240;
dmaSetting.TCD->CITER = 320*240;
dmaSetting.TCD->DADDR = &LPSPI4_TDR; // FIXME is this correct?
dmaSetting.TCD->DOFF = 0;
dmaSetting.TCD->ATTR_DST = 1;
dmaSetting.TCD->DLASTSGA = 0;
// Make it loop on itself
dmaSetting.replaceSettingsOnCompletion(dmaSetting);
dmatx.begin(false);
dmatx.triggerAtHardwareEvent(DMAMUX_SOURCE_LPSPI4_TX); // FIXME what's the right source ID
dmatx = &dmaSetting;
#endif
// LCD initialization complete
clrScr();
tft.fillScreen(ILI9341_BLACK);
driveIndicator[0] = driveIndicator[1] = false;
driveIndicatorDirty = true;
@ -111,8 +150,7 @@ void TeensyDisplay::redraw()
void TeensyDisplay::clrScr()
{
// FIXME: only fill the area that's got our "terminal"
tft.fillScreen(ILI9341_BLACK);
memset(dmaBuffer, 0x00, sizeof(dmaBuffer));
}
void TeensyDisplay::drawUIPixel(uint16_t x, uint16_t y, uint16_t color)
@ -132,48 +170,30 @@ void TeensyDisplay::drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint
drawPixel(x,y,color16);
}
void TeensyDisplay::flush()
{
blit({0,0,191,279});
}
void TeensyDisplay::blit(AiieRect r)
{
// remember these are "starts at pixel number" values, where 0 is the first.
#define HOFFSET 18
#define VOFFSET 13
// The goal here is for blitting to happen automatically in DMA transfers.
// Since that isn't the case yet, here's a manual blit of the whole
// screen (b/c the rect is kinda meaningless in the final "draw
// everything always" DMA mode)
tft.writeRect(0,0,320,240,(const uint16_t *)dmaBuffer);
uint8_t *vbufPtr;
for (uint8_t y=r.top; y<=r.bottom; y++) {
vbufPtr = &videoBuffer[y * TEENSY_DRUN + r.left];
for (uint16_t x=r.left; x<=r.right; x++) {
uint8_t colorIdx;
if (!(x & 0x01)) {
colorIdx = *vbufPtr >> 4;
} else {
// alpha the right-ish pixel over the left-ish pixel.
colorIdx = *vbufPtr & 0x0F;
}
colorIdx <<= 1;
uint16_t c;
if (g_displayType == m_monochrome) {
c = loresPixelColorsGreen[colorIdx];
}
else if (g_displayType == m_blackAndWhite) {
c = loresPixelColorsWhite[colorIdx];
} else {
c = loresPixelColors[colorIdx];
}
drawPixel(x+HOFFSET,y+VOFFSET,c);
if (x & 0x01) {
// When we do the odd pixels, then move the pixel pointer to the next pixel
vbufPtr++;
// draw overlay, if any, occasionally
{
static uint32_t nextMessageTime = 0;
if (millis() >= nextMessageTime) {
if (overlayMessage[0]) {
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
nextMessageTime = millis() + 1000;
}
}
// draw overlay, if any
if (overlayMessage[0]) {
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
}
void TeensyDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
@ -212,9 +232,9 @@ void TeensyDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
uint8_t ch = pgm_read_byte(&BiosFont[temp]);
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
drawPixel(x+x_off, y+y_off, onPixel);
dmaBuffer[y+y_off][x+x_off] = onPixel;
} else {
drawPixel(x+x_off, y+y_off, offPixel);
dmaBuffer[y+y_off][x+x_off] = offPixel;
}
}
temp++;
@ -242,27 +262,19 @@ void TeensyDisplay::drawImageOfSizeAt(const uint8_t *img,
r = pgm_read_byte(&img[(y*sizex + x)*3 + 0]);
g = pgm_read_byte(&img[(y*sizex + x)*3 + 1]);
b = pgm_read_byte(&img[(y*sizex + x)*3 + 2]);
drawPixel(wherex+x, wherey+y, (((r&248)|g>>5) << 8) | ((g&28)<<3|b>>3));
dmaBuffer[y+wherey][x+wherex] = RGBto565(r,g,b);
}
}
}
// "DoubleWide" means "please double the X because I'm in low-res
// width mode". But we only have half the horizontal width required on
// the Teensy, so it's divided in half. And then we drop to 4-bit
// colors, so it's divided in half again.
// the Teensy, so it's divided in half.
void TeensyDisplay::cacheDoubleWidePixel(uint16_t x, uint16_t y, uint8_t color)
{
uint8_t b = videoBuffer[y*TEENSY_DRUN+(x>>1)];
if (x & 1) {
// Low nybble
b = (b & 0xF0) | (color & 0x0F);
} else {
// High nybble
b = (color << 4) | (b & 0x0F);
}
videoBuffer[y*TEENSY_DRUN+(x>>1)] = b;
uint16_t color16;
color16 = loresPixelColors[(( color & 0x0F ) )];
dmaBuffer[y+VOFFSET][x+HOFFSET] = color16;
}
// This exists for 4bpp optimization. We could totally call
@ -271,32 +283,31 @@ void TeensyDisplay::cacheDoubleWidePixel(uint16_t x, uint16_t y, uint8_t color)
void TeensyDisplay::cache2DoubleWidePixels(uint16_t x, uint16_t y,
uint8_t colorA, uint8_t colorB)
{
videoBuffer[y*TEENSY_DRUN+(x>>1)] = (colorB << 4) | colorA;
// FIXME: Convert 4-bit colors to 16-bit colors?
dmaBuffer[y+VOFFSET][x+ HOFFSET] = loresPixelColors[colorB&0xF];
dmaBuffer[y+VOFFSET][x+1+HOFFSET] = loresPixelColors[colorA&0xF];
}
// This is the full 560-pixel-wide version -- and we only have 280
// pixels wide. So we'll divide x by 2. And then at 4bpp, we divide by
// 2 again.
// On odd-numbered X pixels, we also alpha-blend -- "black" means "clear"
// pixels in our buffer b/c the display is only 320 pixels wide
// itself. So we'll divide x by 2. On odd-numbered X pixels, we also
// alpha-blend -- "black" means "clear"
void TeensyDisplay::cachePixel(uint16_t x, uint16_t y, uint8_t color)
{
if (x&1) {
x >>= 1; // divide by 2, then this is mostly cacheDoubleWidePixel. Except...
uint8_t b = videoBuffer[y*TEENSY_DRUN+(x>>1)];
if (x & 1) {
// Low nybble
if (color == c_black)
color = b & 0x0F;
b = (b & 0xF0) | (color & 0x0F);
} else {
// High nybble
if (color == c_black)
color = (b & 0xF0) >> 4;
b = (color << 4) | (b & 0x0F);
}
videoBuffer[y*TEENSY_DRUN+(x>>1)] = b;
if (/*x&*/1) {
// divide x by 2, then this is mostly cacheDoubleWidePixel. Except
// we also have to do the alpha blend so we can see both pixels.
uint16_t *p = &dmaBuffer[y+VOFFSET][(x>>1)+HOFFSET];
uint16_t destColor = loresPixelColors[color];
// if (color == 0)
// destColor = *p; // retain the even-numbered pixel's contents ("alpha blend")
// Otherwise the odd-numbered pixel's contents "win" as "last drawn"
// FIXME: do better blending of these two pixels.
dmaBuffer[y+VOFFSET][(x>>1)+HOFFSET] = destColor;
} else {
cacheDoubleWidePixel(x/2, y, color);
cacheDoubleWidePixel(x, y, color);
}
}

View File

@ -3,16 +3,9 @@
#include <Arduino.h>
#include <ILI9341_t3.h>
#include "physicaldisplay.h"
#define TEENSY_DHEIGHT 192
#define TEENSY_DWIDTH 280
// run length of one row of pixels
#define TEENSY_DRUN (TEENSY_DWIDTH/2)
#define regtype volatile uint8_t
#define regsize uint8_t
class UTFT;
class BIOS;
@ -27,7 +20,7 @@ class TeensyDisplay : public PhysicalDisplay {
virtual void redraw();
virtual void clrScr();
virtual void flush() {};
virtual void flush();
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);
@ -46,8 +39,6 @@ class TeensyDisplay : public PhysicalDisplay {
bool needsRedraw;
bool driveIndicator[2];
bool driveIndicatorDirty;
uint8_t videoBuffer[TEENSY_DHEIGHT * TEENSY_DWIDTH / 2];
};
#endif

View File

@ -118,12 +118,13 @@ int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char *o
// multiple suffixes to check - all must be 3 chars long, FIXME
bool matchesAny = false;
const char *p = suffix;
while (*p && strlen(p)) {
while (p && *p && strlen(p)) {
if (!strncasecmp(fsuff, p, 3)) {
matchesAny = true;
break;
}
p = strstr(p, ",")+1;
p = strstr(p, ",");
if (p) p++;
}
if (!matchesAny) {
e.close();

View File

@ -38,13 +38,17 @@ static time_t getTeensy3Time() { return Teensy3Clock.get(); }
void setup()
{
Serial.begin(230400);
/*
#if 0
// Wait for USB serial connection before booting while debugging
while (!Serial) {
yield();
}*/
delay(100); // let the power settle
}
#endif
delay(120); // let the power settle
// enableFaultHandler();
SCB_SHCSR |= SCB_SHCSR_BUSFAULTENA | SCB_SHCSR_USGFAULTENA | SCB_SHCSR_MEMFAULTENA;
enableFaultHandler();
// set the Time library to use Teensy 3.0's RTC to keep time
setSyncProvider(getTeensy3Time);
@ -69,27 +73,45 @@ void setup()
pinMode(BATTERYPIN, INPUT);
*/
Serial.print("Free RAM: ");
println(FreeRamEstimate());
println("creating virtual hardware");
g_speaker = new TeensySpeaker(SPEAKERPIN);
Serial.print("Free RAM: ");
println(FreeRamEstimate());
println(" fm");
// First create the filemanager - the interface to the host file system.
g_filemanager = new TeensyFileManager();
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// Construct the interface to the host display. This will need the
// VM's video buffer in order to draw the VM, but we don't have that
// yet.
println(" display");
g_display = new TeensyDisplay();
Serial.print("Free RAM: ");
println(FreeRamEstimate());
println(" UI");
g_ui = new AppleUI();
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// Next create the virtual CPU. This needs the VM's MMU in order to
// run, but we don't have that yet.
println(" cpu");
g_cpu = new Cpu();
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// Create the virtual machine. This may read from g_filemanager to
// get ROMs if necessary. (The actual Apple VM we've built has them
// compiled in, though.) It will create its virutal hardware (MMU,
@ -97,18 +119,31 @@ void setup()
println(" vm");
g_vm = new AppleVM();
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// Now that the VM exists and it has created an MMU, we tell the CPU
// how to access memory through the MMU.
println(" [setMMU]");
g_cpu->SetMMU(g_vm->getMMU());
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// And the physical keyboard needs hooks in to the virtual keyboard...
println(" keyboard");
g_keyboard = new TeensyKeyboard(g_vm->getKeyboard());
Serial.print("Free RAM: ");
println(FreeRamEstimate());
println(" paddles");
g_paddles = new TeensyPaddles(A3, A4, 1, 1);
Serial.print("Free RAM: ");
println(FreeRamEstimate());
// Now that all the virtual hardware is glued together, reset the VM
println("Resetting VM");
g_vm->Reset();
@ -122,8 +157,8 @@ void setup()
startMicros = nextInstructionMicros = micros();
// Debugging: insert a disk on startup...
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
//((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
//((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
// pinMode(56, OUTPUT);
@ -271,10 +306,11 @@ void loop()
g_ui->blit();
g_vm->vmdisplay->lockDisplay();
if (g_vm->vmdisplay->needsRedraw()) {
AiieRect what = g_vm->vmdisplay->getDirtyRect();
g_vm->vmdisplay->didRedraw();
g_display->blit(what);
// AiieRect what = g_vm->vmdisplay->getDirtyRect();
// g_vm->vmdisplay->didRedraw();
// g_display->blit(what);
}
g_display->blit({0,0,191,279});
g_vm->vmdisplay->unlockDisplay();
if (g_prioritizeDisplay)
Timer1.start();

View File

@ -1,6 +1,7 @@
#ifdef TEENSYDUINO
#include <Arduino.h>
#include "teensy-println.h"
EXTMEM uint8_t preallocatedRam[591*256];
#endif
#include "vmram.h"

View File

@ -36,7 +36,10 @@ class VMRam {
// Pages 0-3 are ZP; we want those in RAM.
// Pages 4-7 are 0x200 - 0x3FF. We want those in RAM too (text pages).
uint8_t preallocatedRam[591*256];
// Has to be static if we're using the EXTMEM sectioning, so it's now in vmram.cpp :/
//EXTMEM uint8_t preallocatedRam[591*256];
};