Support RGB mode 14. Mix if mono with no NTSC artifacts and color

This commit is contained in:
Ivan Izaguirre 2020-08-06 18:35:34 +02:00
parent dad2a67321
commit 6bfa1d2986
8 changed files with 158 additions and 34 deletions

View File

@ -22,7 +22,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- 1Mb Memory Expansion Card - 1Mb Memory Expansion Card
- ThunderClock Plus real time clock - ThunderClock Plus real time clock
- Bootable hard disk card - Bootable hard disk card
- Apple //e 80 columns with 64Kb extra RAM - Apple //e 80 columns with 64Kb extra RAM and optional RGB modes
- VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb - VidHd, limited to the ROM signature and SHR as used by Total Replay, only for //e models with 128Kb
- FASTChip, limited to what Total Replay needs to set and clear fast mode - FASTChip, limited to what Total Replay needs to set and clear fast mode
- Graphic modes: - Graphic modes:
@ -34,16 +34,19 @@ 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 mode 11, mono 560x192
- RGB card mode 13, ntsc 140*192 (regular DHGR)
- RGB card mode 14, mix of modes 11 and 13 on the fly
- Displays: - Displays:
- Green monochrome monitor with half width pixel support - Green monochrome monitor with half width pixel support
- NTSC Color TV (extracting the phase from the mono signal) - NTSC Color TV (extracting the phase from the mono signal)
- RGB for Super High Resolution - RGB for Super High Resolution and RGB card
- ANSI Console, avoiding the SDL2 dependency - ANSI Console, avoiding the SDL2 dependency
- Other features: - Other features:
- Sound - Sound
- Joystick support. Up to two joysticks or four paddles. - Joystick support. Up to two joysticks or four paddles
- Adjustable speed. - Adjustable speed
- Fast disk mode to set max speed while using the disks. - Fast disk mode to set max speed while using the disks
- Single file executable with embedded ROMs and DOS 3.3 - Single file executable with embedded ROMs and DOS 3.3
- Pause (thanks a2geek) - Pause (thanks a2geek)
- ProDOS MLI calls tracing - ProDOS MLI calls tracing
@ -142,11 +145,11 @@ Only valid on SDL mode
-diskRom string -diskRom string
rom file for the disk drive controller (default "<internal>/DISK2.rom") rom file for the disk drive controller (default "<internal>/DISK2.rom")
-diskb string -diskb string
file to load on the second disk drive file to load on the second disk drive
-dumpChars -dumpChars
shows the character map shows the character map
-fastChipSlot int -fastChipSlot int
slot for the FASTChip accelerator card, -1 for none (default 3) slot for the FASTChip accelerator card, -1 for none (default 3)
-fastDisk -fastDisk
set fast mode when the disks are spinning (default true) set fast mode when the disks are spinning (default true)
-hd string -hd string
@ -156,7 +159,7 @@ Only valid on SDL mode
-languageCardSlot int -languageCardSlot int
slot for the 16kb language card. -1 for none slot for the 16kb language card. -1 for none
-memoryExpSlot int -memoryExpSlot int
slot for the Memory Expansion card with 1GB. -1 for none (default 4) slot for the Memory Expansion card with 1GB. -1 for none (default 4)
-mhz float -mhz float
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857) cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
-model string -model string
@ -167,6 +170,8 @@ Only valid on SDL mode
panic if a not implemented softswitch is used panic if a not implemented softswitch is used
-profile -profile
generate profile trace to analyse with pprof generate profile trace to analyse with pprof
-rgb
emulate the RGB modes of the 80col RGB card for DHGR (default true)
-rom string -rom string
main rom file (default "<default>") main rom file (default "<default>")
-saturnCardSlot int -saturnCardSlot int
@ -178,13 +183,13 @@ Only valid on SDL mode
-traceHD -traceHD
dump to the console the hd commands dump to the console the hd commands
-traceMLI -traceMLI
dump to the console the calls to ProDOS machine langunage interface calls to $BF00 dump to the console the calls to ProDOS machine language interface calls to $BF00
-traceSS -traceSS
dump to the console the sofswitches calls dump to the console the sofswitches calls
-vidHDSlot int -vidHDSlot int
slot for the VidHD card, only for //e models. -1 for none (default 2) slot for the VidHD card, only for //e models. -1 for none (default 2)
-woz string -woz string
show WOZ file information show WOZ file information
``` ```

View File

@ -189,6 +189,11 @@ func (a *Apple2) AddThunderClockPlusCard(slot int, romFile string) error {
return nil return nil
} }
// AddRGBCard inserts an RBG option to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRGBCard() {
setupRGBCard(a)
}
// AddCardLogger inserts a fake card that logs accesses // AddCardLogger inserts a fake card that logs accesses
func (a *Apple2) AddCardLogger(slot int) { func (a *Apple2) AddCardLogger(slot int) {
a.insertCard(&cardLogger{}, slot) a.insertCard(&cardLogger{}, slot)

View File

@ -77,6 +77,10 @@ func MainApple() *Apple2 {
"mono", "mono",
false, false,
"emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle.") "emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle.")
rgbCard := flag.Bool(
"rgb",
true,
"emulate the RGB modes of the 80col RGB card for DHGR")
fastDisk := flag.Bool( fastDisk := flag.Bool(
"fastDisk", "fastDisk",
true, true,
@ -112,7 +116,7 @@ func MainApple() *Apple2 {
traceMLI := flag.Bool( traceMLI := flag.Bool(
"traceMLI", "traceMLI",
false, false,
"dump to the console the calls to ProDOS machine langunage interface calls to $BF00") "dump to the console the calls to ProDOS machine language interface calls to $BF00")
flag.Parse() flag.Parse()
@ -249,6 +253,10 @@ func MainApple() *Apple2 {
} }
} }
if *rgbCard {
a.AddRGBCard()
}
//a.AddCardInOut(2) //a.AddCardInOut(2)
//a.AddCardLogger(4) //a.AddCardLogger(4)

72
cardRGB.go Normal file
View File

@ -0,0 +1,72 @@
package apple2
/*
Extended 80-Column Text AppleColor Card or Video7 RGB-SL7 card
See:
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Apple%20IIe/Apple%20IIe%20Extended%2080%20Column%20RGB%20Card/Manuals/Apple%20Ext80ColumnAppleColorCardHR%20Manual.pdf
https://apple2online.com/web_documents/Video-7%20Manual%20KB.pdf
https://mirrors.apple2.org.za/ftp.apple.asimov.net/documentation/hardware/video/DIGICARD%2064K%20Extended%2080%20Column%20RGB%20Card%20for%20Apple%20IIe%20Instruction%20Manual.pdf
It goes to the 80 column slot.
To set the state it AN3 in graphics mode has to go off-on-off-on. Each pair off-on record the state of 80col:
on step 0, an ANN3OFF moves to step 1
on step 1, an ANN3ON moves to step 2, and the value of 80COL is copied to RGB flag 1
on step 2, an ANN3OFF moves to step 3
on step 3, an ANN3ON moves to step 4, and the value of 80COL is copied to RGB flag 2
Modes by RGB flags 1 and 2:
0-0: 560*192 mono
1-1: 140*192 ntsc
0-1: Mixed mode
1-0: 160*192 ntsc (not supported)
*/
type cardRGB struct {
// cardBase, not a regular card
step uint8
}
func setupRGBCard(a *Apple2) *cardRGB {
var c cardRGB
c.step = 0
// Does not have ROM or private softswitches. It spies on the softswitches
a.io.addSoftSwitchRW(0x50, func(io *ioC0Page) uint8 {
io.softSwitchesData[ioFlagText] = ssOff
// Reset RGB modes when entering graphics mode
c.step = 0
io.softSwitchesData[ioFlag1RGBCard] = ssOn
io.softSwitchesData[ioFlag2RGBCard] = ssOn
return 0
}, "TEXTOFF")
a.io.addSoftSwitchRW(0x5e, func(io *ioC0Page) uint8 {
io.softSwitchesData[ioFlagAnnunciator3] = ssOff
switch c.step {
case 0:
c.step++
case 2:
c.step++
}
return 0
}, "ANN3OFF-RGB")
a.io.addSoftSwitchRW(0x5f, func(io *ioC0Page) uint8 {
io.softSwitchesData[ioFlagAnnunciator3] = ssOn
switch c.step {
case 1:
io.softSwitchesData[ioFlag1RGBCard] = io.softSwitchesData[ioFlag80Col]
c.step++
case 3:
io.softSwitchesData[ioFlag2RGBCard] = io.softSwitchesData[ioFlag80Col]
c.step++
}
return 0
}, "ANN3ON-RGB")
return &c
}

View File

@ -30,6 +30,11 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo) isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
var lightColor color.Color var lightColor color.Color
if isColor { if isColor {
lightColor = color.White lightColor = color.White
@ -41,6 +46,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
} }
var snap *image.RGBA var snap *image.RGBA
var ntscMask *image.Alpha
if isSuperHighResMode { // Has to be first and disables the rest if isSuperHighResMode { // Has to be first and disables the rest
snap = snapshotSuperHiResMode(a) snap = snapshotSuperHiResMode(a)
} else if isTextMode { } else if isTextMode {
@ -48,7 +54,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
} else { } else {
if isHiResMode { if isHiResMode {
if isDoubleResMode { if isDoubleResMode {
snap = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, lightColor) snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, isRGBMixMode, lightColor)
} else { } else {
snap = snapshotHiResModeMono(a, isSecondPage, isMixMode, lightColor) snap = snapshotHiResModeMono(a, isSecondPage, isMixMode, lightColor)
} }
@ -60,8 +66,8 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor) snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor)
snap = mixSnapshots(snap, snapText) snap = mixSnapshots(snap, snapText)
} }
if isColor && !raw { if isColor && !(raw || isMono560) {
snap = filterNTSCColor(false /*blacker*/, snap) snap = filterNTSCColor(snap, ntscMask)
} }
} }
@ -71,15 +77,15 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
return snap return snap
} }
// SnapshotAllModes to get all modes mixed // SnapshotHGRModes to get all modes mixed
func SnapshotHGRModes(a *Apple2) *image.RGBA { func SnapshotHGRModes(a *Apple2) *image.RGBA {
bwSnap := activeSnapshot(a, true) bwSnap := activeSnapshot(a, true)
if bwSnap.Bounds().Dx() == hiResWidth { if bwSnap.Bounds().Dx() == hiResWidth {
bwSnap = doubleWidthFilter(bwSnap) bwSnap = doubleWidthFilter(bwSnap)
} }
colorSnap := filterNTSCColor(false, bwSnap) colorSnap := filterNTSCColor(bwSnap, nil)
page1Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White)) // HGR 1 page1Snap := filterNTSCColor(snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White), nil) // HGR 1
page2Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White)) // HGR 2 page2Snap := filterNTSCColor(snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White), nil) // HGR 2
size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2) size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2)
out := image.NewRGBA(size) out := image.NewRGBA(size)

View File

@ -9,15 +9,21 @@ const (
doubleHiResWidth = 2 * hiResWidth doubleHiResWidth = 2 * hiResWidth
) )
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light color.Color) *image.RGBA { func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
// As described in "Inside the Apple IIe" // As described in "Inside the Apple IIe"
height := hiResHeight height := hiResHeight
if mixedMode { if mixedMode {
height = hiResHeightMixed height = hiResHeightMixed
} }
size := image.Rect(0, 0, doubleHiResWidth, height) size := image.Rect(0, 0, doubleHiResWidth, height)
// To support RGB-mode14 we will have a mask to mark where we should not have the NTSC filter applied
// See: https://apple2online.com/web_documents/Video-7%20Manual%20KB.pdf
var ntscMask *image.Alpha
if getNTSCMask {
ntscMask = image.NewAlpha(size)
}
img := image.NewRGBA(size) img := image.NewRGBA(size)
for y := 0; y < height; y++ { for y := 0; y < height; y++ {
lineParts := [][]uint8{ lineParts := [][]uint8{
@ -27,21 +33,34 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, l
x := 0 x := 0
// For the NTSC filter to work we have to insert an initial black pixel and skip the last one // For the NTSC filter to work we have to insert an initial black pixel and skip the last one
img.Set(x, y, color.Black) img.Set(x, y, color.Black)
if getNTSCMask {
ntscMask.Set(x, y, color.Opaque)
}
x++ x++
for iByte := 0; iByte < hiResLineBytes; iByte++ { for iByte := 0; iByte < hiResLineBytes; iByte++ {
for iPart := 0; iPart < 2; iPart++ { for iPart := 0; iPart < 2; iPart++ {
b := lineParts[iPart][iByte] b := lineParts[iPart][iByte]
mask := color.Transparent // Apply the NTSC filter
if getNTSCMask && b&0x80 == 0 {
mask = color.Opaque // Do not apply the NTSC filter
}
for j := uint(0); j < 7; j++ { for j := uint(0); j < 7; j++ {
// Set color
bit := (b >> j) & 1 bit := (b >> j) & 1
colour := light colour := light
if bit == 0 { if bit == 0 {
colour = color.Black colour = color.Black
} }
img.Set(x, y, colour) img.Set(x, y, colour)
// Set mask if requested
if getNTSCMask {
ntscMask.Set(x, y, mask)
}
x++ x++
} }
} }
} }
} }
return img return img, ntscMask
} }

View File

@ -54,7 +54,7 @@ func getNTSCColorMap() []color.Color {
return colorMap return colorMap
} }
func filterNTSCColor(blacker bool, in *image.RGBA) *image.RGBA { func filterNTSCColor(in *image.RGBA, mask *image.Alpha) *image.RGBA {
colorMap := getNTSCColorMap() colorMap := getNTSCColorMap()
b := in.Bounds() b := in.Bounds()
@ -69,19 +69,21 @@ func filterNTSCColor(blacker bool, in *image.RGBA) *image.RGBA {
r, _, _, _ := cIn.RGBA() r, _, _, _ := cIn.RGBA()
pos := 1 << (3 - uint(x%4)) pos := 1 << (3 - uint(x%4))
var cOut color.Color
if r != 0 { if r != 0 {
v |= pos v |= pos
cOut = colorMap[v]
} else { } else {
v &^= pos v &^= pos
if blacker { }
// If there is no luminance, let's have black anyway
cOut = colorMap[0] cOut := colorMap[v]
} else { if mask != nil {
cOut = colorMap[v] // RGM mode7
_, _, _, a := mask.At(x, y).RGBA()
if a > 0 {
cOut = cIn
} }
} }
out.Set(x, y, cOut) out.Set(x, y, cOut)
} }

View File

@ -7,7 +7,7 @@ const (
ioFlagMixed uint8 = 0x52 ioFlagMixed uint8 = 0x52
ioFlagSecondPage uint8 = 0x54 ioFlagSecondPage uint8 = 0x54
ioFlagHiRes uint8 = 0x56 ioFlagHiRes uint8 = 0x56
ioFlagAnnunciator0 uint8 = 0x58 // On Copam Electronics Base-64A this is used to bank swith the ROM ioFlagAnnunciator0 uint8 = 0x58
ioFlagAnnunciator1 uint8 = 0x5a ioFlagAnnunciator1 uint8 = 0x5a
ioFlagAnnunciator2 uint8 = 0x5c ioFlagAnnunciator2 uint8 = 0x5c
ioFlagAnnunciator3 uint8 = 0x5e ioFlagAnnunciator3 uint8 = 0x5e
@ -20,6 +20,9 @@ const (
ioDataPaddle1 uint8 = 0x65 ioDataPaddle1 uint8 = 0x65
ioDataPaddle2 uint8 = 0x66 ioDataPaddle2 uint8 = 0x66
ioDataPaddle3 uint8 = 0x67 ioDataPaddle3 uint8 = 0x67
ioFlag1RGBCard uint8 = 0x7e // Not real softSwitches. Using the numbers to store the flags somewhere.
ioFlag2RGBCard uint8 = 0x7f
) )
func addApple2SoftSwitches(io *ioC0Page) { func addApple2SoftSwitches(io *ioC0Page) {
@ -71,6 +74,10 @@ func addApple2SoftSwitches(io *ioC0Page) {
io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3), "PDL3") io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3), "PDL3")
io.addSoftSwitchR(0x70, strobePaddlesSoftSwitch, "RESETPDL") // Game controllers reset io.addSoftSwitchR(0x70, strobePaddlesSoftSwitch, "RESETPDL") // Game controllers reset
// For RGB screen modes. Default to NTSC artifacts
io.softSwitchesData[ioFlag1RGBCard] = ssOn
io.softSwitchesData[ioFlag2RGBCard] = ssOn
} }
func notImplementedSoftSwitchR(*ioC0Page) uint8 { func notImplementedSoftSwitchR(*ioC0Page) uint8 {
@ -141,9 +148,9 @@ func getButtonSoftSwitch(i int) softSwitchR {
/* /*
Paddle values are calculated by the time taken by a current going Paddle values are calculated by the time taken by a current going
througt the paddle variable resistor to charge a capacitor. through the paddle variable resistor to charge a capacitor.
The capacitor is discharged via the strobe softswitch. The result is The capacitor is discharged via the strobe softswitch. The result is
hoy many times a 11 cycles loop runs before the capacitor reaches how many times a 11 cycles loop runs before the capacitor reaches
the voltage threshold. the voltage threshold.
See: http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.06.html See: http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.06.html