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 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 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, uint16_t color) = 0;
virtual void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) = 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 <ctype.h> // isgraph
#include <DMAChannel.h>
#include "teensy-display.h" #include "teensy-display.h"
#include "bios-font.h" #include "bios-font.h"
#include "appleui.h" #include "appleui.h"
#include <SPI.h>
#define _clock 65000000 #define _clock 65000000
@ -13,14 +16,28 @@
#define PIN_MISO 12 #define PIN_MISO 12
#define PIN_SCK 13 #define PIN_SCK 13
#define disp_x_size 239 // Inside the 320x240 display, the Apple display is 280x192.
#define disp_y_size 319 // (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 "globals.h"
#include "applevm.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); 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 // RGB map of each of the lowres colors
const uint16_t loresPixelColors[16] = { 0x0000, // 0 black const uint16_t loresPixelColors[16] = { 0x0000, // 0 black
0xC006, // 1 magenta 0xC006, // 1 magenta
@ -78,18 +95,40 @@ const uint16_t loresPixelColorsWhite[16] = { 0x0000,
TeensyDisplay::TeensyDisplay() TeensyDisplay::TeensyDisplay()
{ {
memset(videoBuffer, 0, sizeof(videoBuffer)); memset(dmaBuffer, 0x80, sizeof(dmaBuffer));
tft.begin(); tft.begin();
tft.setRotation(3); tft.setRotation(3);
tft.setClock(_clock); 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 // 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 // LCD initialization complete
clrScr(); tft.fillScreen(ILI9341_BLACK);
driveIndicator[0] = driveIndicator[1] = false; driveIndicator[0] = driveIndicator[1] = false;
driveIndicatorDirty = true; driveIndicatorDirty = true;
@ -111,8 +150,7 @@ void TeensyDisplay::redraw()
void TeensyDisplay::clrScr() void TeensyDisplay::clrScr()
{ {
// FIXME: only fill the area that's got our "terminal" memset(dmaBuffer, 0x00, sizeof(dmaBuffer));
tft.fillScreen(ILI9341_BLACK);
} }
void TeensyDisplay::drawUIPixel(uint16_t x, uint16_t y, uint16_t color) 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); drawPixel(x,y,color16);
} }
void TeensyDisplay::flush()
{
blit({0,0,191,279});
}
void TeensyDisplay::blit(AiieRect r) void TeensyDisplay::blit(AiieRect r)
{ {
// remember these are "starts at pixel number" values, where 0 is the first. // The goal here is for blitting to happen automatically in DMA transfers.
#define HOFFSET 18
#define VOFFSET 13 // 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; // draw overlay, if any, occasionally
for (uint8_t y=r.top; y<=r.bottom; y++) { {
vbufPtr = &videoBuffer[y * TEENSY_DRUN + r.left]; static uint32_t nextMessageTime = 0;
for (uint16_t x=r.left; x<=r.right; x++) { if (millis() >= nextMessageTime) {
uint8_t colorIdx; if (overlayMessage[0]) {
if (!(x & 0x01)) { drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
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++;
} }
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) 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]); uint8_t ch = pgm_read_byte(&BiosFont[temp]);
for (int8_t x_off = 0; x_off <= xsize; x_off++) { for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-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 { } else {
drawPixel(x+x_off, y+y_off, offPixel); dmaBuffer[y+y_off][x+x_off] = offPixel;
} }
} }
temp++; temp++;
@ -242,27 +262,19 @@ void TeensyDisplay::drawImageOfSizeAt(const uint8_t *img,
r = pgm_read_byte(&img[(y*sizex + x)*3 + 0]); r = pgm_read_byte(&img[(y*sizex + x)*3 + 0]);
g = pgm_read_byte(&img[(y*sizex + x)*3 + 1]); g = pgm_read_byte(&img[(y*sizex + x)*3 + 1]);
b = pgm_read_byte(&img[(y*sizex + x)*3 + 2]); 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 // "DoubleWide" means "please double the X because I'm in low-res
// width mode". But we only have half the horizontal width required on // 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 // the Teensy, so it's divided in half.
// colors, so it's divided in half again.
void TeensyDisplay::cacheDoubleWidePixel(uint16_t x, uint16_t y, uint8_t color) void TeensyDisplay::cacheDoubleWidePixel(uint16_t x, uint16_t y, uint8_t color)
{ {
uint8_t b = videoBuffer[y*TEENSY_DRUN+(x>>1)]; uint16_t color16;
color16 = loresPixelColors[(( color & 0x0F ) )];
if (x & 1) { dmaBuffer[y+VOFFSET][x+HOFFSET] = color16;
// Low nybble
b = (b & 0xF0) | (color & 0x0F);
} else {
// High nybble
b = (color << 4) | (b & 0x0F);
}
videoBuffer[y*TEENSY_DRUN+(x>>1)] = b;
} }
// This exists for 4bpp optimization. We could totally call // 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, void TeensyDisplay::cache2DoubleWidePixels(uint16_t x, uint16_t y,
uint8_t colorA, uint8_t colorB) 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 // 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 // pixels in our buffer b/c the display is only 320 pixels wide
// 2 again. // itself. So we'll divide x by 2. On odd-numbered X pixels, we also
// On odd-numbered X pixels, we also alpha-blend -- "black" means "clear" // alpha-blend -- "black" means "clear"
void TeensyDisplay::cachePixel(uint16_t x, uint16_t y, uint8_t color) void TeensyDisplay::cachePixel(uint16_t x, uint16_t y, uint8_t color)
{ {
if (x&1) { if (/*x&*/1) {
x >>= 1; // divide by 2, then this is mostly cacheDoubleWidePixel. Except... // divide x by 2, then this is mostly cacheDoubleWidePixel. Except
uint8_t b = videoBuffer[y*TEENSY_DRUN+(x>>1)]; // we also have to do the alpha blend so we can see both pixels.
if (x & 1) { uint16_t *p = &dmaBuffer[y+VOFFSET][(x>>1)+HOFFSET];
// Low nybble uint16_t destColor = loresPixelColors[color];
if (color == c_black)
color = b & 0x0F; // if (color == 0)
b = (b & 0xF0) | (color & 0x0F); // destColor = *p; // retain the even-numbered pixel's contents ("alpha blend")
} else { // Otherwise the odd-numbered pixel's contents "win" as "last drawn"
// High nybble // FIXME: do better blending of these two pixels.
if (color == c_black)
color = (b & 0xF0) >> 4; dmaBuffer[y+VOFFSET][(x>>1)+HOFFSET] = destColor;
b = (color << 4) | (b & 0x0F);
}
videoBuffer[y*TEENSY_DRUN+(x>>1)] = b;
} else { } else {
cacheDoubleWidePixel(x/2, y, color); cacheDoubleWidePixel(x, y, color);
} }
} }

View File

@ -3,16 +3,9 @@
#include <Arduino.h> #include <Arduino.h>
#include <ILI9341_t3.h> #include <ILI9341_t3.h>
#include "physicaldisplay.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 UTFT;
class BIOS; class BIOS;
@ -27,7 +20,7 @@ class TeensyDisplay : public PhysicalDisplay {
virtual void redraw(); virtual void redraw();
virtual void clrScr(); 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 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); 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 needsRedraw;
bool driveIndicator[2]; bool driveIndicator[2];
bool driveIndicatorDirty; bool driveIndicatorDirty;
uint8_t videoBuffer[TEENSY_DHEIGHT * TEENSY_DWIDTH / 2];
}; };
#endif #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 // multiple suffixes to check - all must be 3 chars long, FIXME
bool matchesAny = false; bool matchesAny = false;
const char *p = suffix; const char *p = suffix;
while (*p && strlen(p)) { while (p && *p && strlen(p)) {
if (!strncasecmp(fsuff, p, 3)) { if (!strncasecmp(fsuff, p, 3)) {
matchesAny = true; matchesAny = true;
break; break;
} }
p = strstr(p, ",")+1; p = strstr(p, ",");
if (p) p++;
} }
if (!matchesAny) { if (!matchesAny) {
e.close(); e.close();

View File

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

View File

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

View File

@ -36,7 +36,10 @@ class VMRam {
// Pages 0-3 are ZP; we want those in RAM. // Pages 0-3 are ZP; we want those in RAM.
// Pages 4-7 are 0x200 - 0x3FF. We want those in RAM too (text pages). // 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];
}; };