RGB card 40 columns text with 16 colors video mode

This commit is contained in:
Ivan Izaguirre 2020-08-08 19:23:35 +02:00
parent aa198877ec
commit 0ef160a56e
6 changed files with 140 additions and 48 deletions

View File

@ -34,6 +34,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- Double-Width High-Resolution graphics (Apple //e only) - Double-Width High-Resolution graphics (Apple //e only)
- Super High Resolution (VidHD only) - Super High Resolution (VidHD only)
- Mixed mode - Mixed mode
- RGB card text 40 columns with 16 colors for foreground and background
- RGB card mode 11, mono 560x192 - RGB card mode 11, mono 560x192
- RGB card mode 13, ntsc 140*192 (regular DHGR) - RGB card mode 13, ntsc 140*192 (regular DHGR)
- RGB card mode 14, mix of modes 11 and 13 on the fly - RGB card mode 14, mix of modes 11 and 13 on the fly

View File

@ -123,7 +123,7 @@ func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
return mmu.getPhysicalMainRAM(mmu.altZeroPage) return mmu.getPhysicalMainRAM(mmu.altZeroPage)
} }
if mmu.store80Active && address <= addressLimitHgr { if mmu.store80Active && address <= addressLimitHgr {
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage) altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage) // TODO: move flag to mmu property like the store80
if address >= addressStartText && address <= addressLimitText { if address >= addressStartText && address <= addressLimitText {
return mmu.getPhysicalMainRAM(altPage) return mmu.getPhysicalMainRAM(altPage)
} }

View File

@ -24,9 +24,10 @@ const (
videoDGR uint8 = 0x09 videoDGR uint8 = 0x09
videoDHGR uint8 = 0x0a videoDHGR uint8 = 0x0a
videoMono560 uint8 = 0x10 videoRGBText40 uint8 = 0x10
videoRGBMix uint8 = 0x11 videoMono560 uint8 = 0x11
videoSHR uint8 = 0x12 videoRGBMix uint8 = 0x12
videoSHR uint8 = 0x13
// Modifiers // Modifiers
videoBaseMask uint8 = 0x1f videoBaseMask uint8 = 0x1f
@ -56,7 +57,11 @@ func getCurrentVideoMode(a *Apple2) uint8 {
if is80Columns { if is80Columns {
mode = videoText80 mode = videoText80
} else { } else {
mode = videoText40 if a.mmu.store80Active {
mode = videoRGBText40
} else {
mode = videoText40
}
} }
isMixMode = false isMixMode = false
} else if isHiResMode { } else if isHiResMode {
@ -111,15 +116,18 @@ func snapshotByMode(a *Apple2, videoMode uint8) *image.RGBA {
var ntscMask *image.Alpha var ntscMask *image.Alpha
switch videoBase { switch videoBase {
case videoText40: case videoText40:
snap = snapshotTextMode(a, false /*is80Columns*/, isSecondPage, lightColor) snap = snapshotText40Mode(a, isSecondPage, lightColor)
applyNTSCFilter = false applyNTSCFilter = false
case videoText80: case videoText80:
snap = snapshotTextMode(a, true /*is80Columns*/, isSecondPage, lightColor) snap = snapshotText80Mode(a, isSecondPage, lightColor)
applyNTSCFilter = false
case videoRGBText40:
snap = snapshotText40RGBMode(a, isSecondPage)
applyNTSCFilter = false applyNTSCFilter = false
case videoGR: case videoGR:
snap = snapshotLoResModeMono(a, false /*isDoubleResMode*/, isSecondPage, lightColor) snap = snapshotLoResModeMono(a, isSecondPage, lightColor)
case videoDGR: case videoDGR:
snap = snapshotLoResModeMono(a, true /*isDoubleResMode*/, isSecondPage, lightColor) snap = snapshotMeResModeMono(a, isSecondPage, lightColor)
case videoHGR: case videoHGR:
snap = snapshotHiResModeMono(a, isSecondPage, lightColor) snap = snapshotHiResModeMono(a, isSecondPage, lightColor)
case videoDHGR: case videoDHGR:
@ -135,8 +143,12 @@ func snapshotByMode(a *Apple2, videoMode uint8) *image.RGBA {
} }
if isMixMode { if isMixMode {
isMix80 := (videoMode & videoMixText80) != 0 var bottom *image.RGBA
bottom := snapshotTextMode(a, isMix80, isSecondPage, lightColor) if (videoMode & videoMixText40) != 0 {
bottom = snapshotText40Mode(a, isSecondPage, lightColor)
} else {
bottom = snapshotText80Mode(a, isSecondPage, lightColor)
}
snap = mixSnapshots(snap, bottom) snap = mixSnapshots(snap, bottom)
} }

View File

@ -7,23 +7,25 @@ import (
// SnapshotParts the currently visible screen // SnapshotParts the currently visible screen
func (a *Apple2) SnapshotParts() *image.RGBA { func (a *Apple2) SnapshotParts() *image.RGBA {
videoMode := getCurrentVideoMode(a) videoMode := getCurrentVideoMode(a)
isSecondPage := (videoMode & videoSecondPage) != 0
videoBase := videoMode & videoBaseMask
snapScreen := snapshotByMode(a, videoMode) snapScreen := snapshotByMode(a, videoMode)
snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage) snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage)
snapPage2 := snapshotByMode(a, videoMode|videoSecondPage) snapPage2 := snapshotByMode(a, videoMode|videoSecondPage)
var snapAux *image.RGBA var snapAux *image.RGBA
/*videoBase := videoMode & videoBaseMask /*
if videoBase == videoRGBMix { if videoBase == videoRGBMix {
isSecondPage := (videoMode & videoSecondPage) != 0 _, mask := snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/ /*, color.White)
_, mask := snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/ /*, color.White)
snapAux = filterMask(mask) snapAux = filterMask(mask)
}*/ }*/
if snapAux == nil && (videoMode&videoMixText80) != 0 { if videoBase == videoRGBText40 {
snapAux = snapshotText40RGBModeColors(a, isSecondPage)
} else if (videoMode & videoMixText80) != 0 {
snapAux = snapshotByMode(a, videoText80) snapAux = snapshotByMode(a, videoText80)
} } else {
if snapAux == nil {
snapAux = snapshotByMode(a, videoText40) snapAux = snapshotByMode(a, videoText40)
} }
@ -45,6 +47,9 @@ func (a *Apple2) VideoModeName() string {
case videoText80: case videoText80:
name = "TEXT80COL" name = "TEXT80COL"
applyNTSCFilter = false applyNTSCFilter = false
case videoRGBText40:
name = "TEXT40COLRGB"
applyNTSCFilter = false
case videoGR: case videoGR:
name = "GR" name = "GR"
case videoDGR: case videoDGR:

View File

@ -38,9 +38,19 @@ func getColorPatterns(light color.Color) [16][16]color.Color {
} }
func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, light color.Color) *image.RGBA { func snapshotLoResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage) data := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
grLines := lines * 2 return renderGrMode(data, false /*isMeres*/, light)
}
func snapshotMeResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
data := getText80FromMemory(a, isSecondPage)
return renderGrMode(data, true /*isMeres*/, light)
}
func renderGrMode(data []uint8, isDoubleResMode bool, light color.Color) *image.RGBA {
grLines := textLines * 2
columns := len(data) / textLines
pixelWidth := loResPixelWidth pixelWidth := loResPixelWidth
if isDoubleResMode { if isDoubleResMode {
pixelWidth = doubleLoResPixelWidth pixelWidth = doubleLoResPixelWidth
@ -52,7 +62,7 @@ func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, l
patterns := getColorPatterns(light) patterns := getColorPatterns(light)
for l := 0; l < grLines; l++ { for l := 0; l < grLines; l++ {
for c := 0; c < columns; c++ { for c := 0; c < columns; c++ {
char := text[(l/2)*columns+c] char := data[(l/2)*columns+c]
grPixel := char >> 4 grPixel := char >> 4
if l%2 == 0 { if l%2 == 0 {
grPixel = char & 0xf grPixel = char & 0xf

View File

@ -19,8 +19,28 @@ const (
textPageSize = uint16(0x0400) textPageSize = uint16(0x0400)
) )
func getTextCharOffset(col int, line int) uint16 { func snapshotText40Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
return renderTextMode(a, text, nil /*colorMap*/, light)
}
func snapshotText80Mode(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
text := getText80FromMemory(a, isSecondPage)
return renderTextMode(a, text, nil /*colorMap*/, light)
}
func snapshotText40RGBMode(a *Apple2, isSecondPage bool) *image.RGBA {
text := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
return renderTextMode(a, text, colorMap, nil)
}
func snapshotText40RGBModeColors(a *Apple2, isSecondPage bool) *image.RGBA {
colorMap := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
return renderTextMode(a, nil /*text*/, colorMap, nil)
}
func getTextCharOffset(col int, line int) uint16 {
// See "Understanding the Apple II", page 5-10 // See "Understanding the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
section := line / 8 // Top, middle and bottom section := line / 8 // Top, middle and bottom
@ -28,26 +48,18 @@ func getTextCharOffset(col int, line int) uint16 {
return uint16(section*40 + eighth*0x80 + col) return uint16(section*40 + eighth*0x80 + col)
} }
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, light color.Color) *image.RGBA { func getText80FromMemory(a *Apple2, isSecondPage bool) []uint8 {
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
return renderTextMode(a, text, columns, lines, light)
}
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool) ([]uint8, int, int) {
if !is80Columns {
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
return text40Columns, textColumns, textLines
}
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage) text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage) text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
// Merge the two 40 cols to return 80 cols // Merge the two 40 cols to return 80 cols
text80Columns := make([]uint8, 2*len(text40Columns)) text80Columns := make([]uint8, 2*len(text40Columns))
for i := 0; i < len(text40Columns); i++ { for i := 0; i < len(text40Columns); i++ {
text80Columns[2*i] = text40ColumnsAlt[i] text80Columns[2*i] = text40ColumnsAlt[i]
text80Columns[2*i+1] = text40Columns[i] text80Columns[2*i+1] = text40Columns[i]
} }
return text80Columns, textColumns * 2, textLines
return text80Columns
} }
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 { func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
@ -68,12 +80,50 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
return text return text
} }
func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color.Color) *image.RGBA { /*
See:
https://mrob.com/pub/xapple2/colors.html
https://archive.org/details/IIgs_2523063_Master_Color_Values
*/
var rgbColorMap = [16]color.Color{
color.RGBA{0, 0, 0, 255}, // Black
color.RGBA{221, 0, 51, 255}, // Magenta
color.RGBA{0, 0, 153, 255}, // Dark Blue
color.RGBA{221, 34, 221, 255}, // Purple
color.RGBA{0, 119, 34, 255}, // Dark Green
color.RGBA{85, 85, 85, 255}, // Grey 1
color.RGBA{34, 34, 255, 255}, // Medium Blue
color.RGBA{102, 170, 255, 255}, // Light Blue
color.RGBA{136, 85, 0, 255}, // Brown
color.RGBA{255, 102, 0, 255}, // Orange
color.RGBA{170, 170, 170, 255}, // Grey 2
color.RGBA{255, 153, 136, 255}, // Pink
color.RGBA{17, 221, 0, 255}, // Green
color.RGBA{255, 255, 0, 255}, // Yellow
color.RGBA{68, 255, 153, 255}, // Aquamarine
color.RGBA{255, 255, 255, 255}, // White
}
func getRGBTextColor(pixel bool, colorKey uint8) color.Color {
if pixel {
colorKey >>= 4
}
colorKey &= 0x0f
return rgbColorMap[colorKey]
}
func renderTextMode(a *Apple2, text []uint8, colorMap []uint8, light color.Color) *image.RGBA {
// Flash mode is 2Hz (host time) // Flash mode is 2Hz (host time)
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2) isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
columns := len(text) / textLines
if text == nil {
columns = textColumns
}
width := columns * charWidth width := columns * charWidth
height := lines * charHeight height := textLines * charHeight
size := image.Rect(0, 0, width, height) size := image.Rect(0, 0, width, height)
img := image.NewRGBA(size) img := image.NewRGBA(size)
@ -83,13 +133,18 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
col := x / charWidth col := x / charWidth
rowInChar := y % charHeight rowInChar := y % charHeight
colInChar := x % charWidth colInChar := x % charWidth
char := text[line*columns+col] charIndex := line*columns + col
var char uint8
if text != nil {
char = text[charIndex]
} else {
char = 79 + 128 // Debug screen filed with O
}
var pixel bool var pixel bool
if a.isApple2e { if a.isApple2e {
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
vid6 := (char & 0x40) != 0 vid6 := (char & 0x40) != 0
vid7 := (char & 0x80) != 0 vid7 := (char & 0x80) != 0
char := char & 0x3f char := char & 0x3f
if vid6 && (vid7 || isAltText) { if vid6 && (vid7 || isAltText) {
char += 0x40 char += 0x40
@ -106,12 +161,16 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
pixel = pixel != (isInverse || (isFlash && isFlashedFrame)) pixel = pixel != (isInverse || (isFlash && isFlashedFrame))
} }
var colour color.Color var colour color.Color
if pixel { if colorMap != nil {
colour = getRGBTextColor(pixel, colorMap[charIndex])
} else if pixel {
colour = light colour = light
} else { } else {
colour = color.Black colour = color.Black
} }
img.Set(x, y, colour) img.Set(x, y, colour)
} }
} }
@ -124,14 +183,19 @@ func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color
func DumpTextModeAnsi(a *Apple2) string { func DumpTextModeAnsi(a *Apple2) string {
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col) is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
content := "\n"
content += fmt.Sprintln(strings.Repeat("#", columns+4))
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar) isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
for l := 0; l < lines; l++ { var text []uint8
if is80Columns {
text = getText80FromMemory(a, isSecondPage)
} else {
text = getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
}
columns := len(text) / textLines
content := "\n"
content += fmt.Sprintln(strings.Repeat("#", columns+4))
for l := 0; l < textLines; l++ {
line := "" line := ""
for c := 0; c < columns; c++ { for c := 0; c < columns; c++ {
char := text[l*columns+c] char := text[l*columns+c]