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
- ThunderClock Plus real time clock
- 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
- FASTChip, limited to what Total Replay needs to set and clear fast mode
- 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)
- Super High Resolution (VidHD only)
- 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:
- Green monochrome monitor with half width pixel support
- 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
- Other features:
- Sound
- Joystick support. Up to two joysticks or four paddles.
- Adjustable speed.
- Fast disk mode to set max speed while using the disks.
- Joystick support. Up to two joysticks or four paddles
- Adjustable speed
- Fast disk mode to set max speed while using the disks
- Single file executable with embedded ROMs and DOS 3.3
- Pause (thanks a2geek)
- ProDOS MLI calls tracing
@ -142,11 +145,11 @@ Only valid on SDL mode
-diskRom string
rom file for the disk drive controller (default "<internal>/DISK2.rom")
-diskb string
file to load on the second disk drive
file to load on the second disk drive
-dumpChars
shows the character map
-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
set fast mode when the disks are spinning (default true)
-hd string
@ -156,7 +159,7 @@ Only valid on SDL mode
-languageCardSlot int
slot for the 16kb language card. -1 for none
-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
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
-model string
@ -167,6 +170,8 @@ Only valid on SDL mode
panic if a not implemented softswitch is used
-profile
generate profile trace to analyse with pprof
-rgb
emulate the RGB modes of the 80col RGB card for DHGR (default true)
-rom string
main rom file (default "<default>")
-saturnCardSlot int
@ -178,13 +183,13 @@ Only valid on SDL mode
-traceHD
dump to the console the hd commands
-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
dump to the console the sofswitches calls
-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
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
}
// 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
func (a *Apple2) AddCardLogger(slot int) {
a.insertCard(&cardLogger{}, slot)

View File

@ -77,6 +77,10 @@ func MainApple() *Apple2 {
"mono",
false,
"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",
true,
@ -112,7 +116,7 @@ func MainApple() *Apple2 {
traceMLI := flag.Bool(
"traceMLI",
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()
@ -249,6 +253,10 @@ func MainApple() *Apple2 {
}
}
if *rgbCard {
a.AddRGBCard()
}
//a.AddCardInOut(2)
//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
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
if isColor {
lightColor = color.White
@ -41,6 +46,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
}
var snap *image.RGBA
var ntscMask *image.Alpha
if isSuperHighResMode { // Has to be first and disables the rest
snap = snapshotSuperHiResMode(a)
} else if isTextMode {
@ -48,7 +54,7 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
} else {
if isHiResMode {
if isDoubleResMode {
snap = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, lightColor)
snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, isRGBMixMode, lightColor)
} else {
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)
snap = mixSnapshots(snap, snapText)
}
if isColor && !raw {
snap = filterNTSCColor(false /*blacker*/, snap)
if isColor && !(raw || isMono560) {
snap = filterNTSCColor(snap, ntscMask)
}
}
@ -71,15 +77,15 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
return snap
}
// SnapshotAllModes to get all modes mixed
// SnapshotHGRModes to get all modes mixed
func SnapshotHGRModes(a *Apple2) *image.RGBA {
bwSnap := activeSnapshot(a, true)
if bwSnap.Bounds().Dx() == hiResWidth {
bwSnap = doubleWidthFilter(bwSnap)
}
colorSnap := filterNTSCColor(false, bwSnap)
page1Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White)) // HGR 1
page2Snap := filterNTSCColor(false /*blacker*/, snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White)) // HGR 2
colorSnap := filterNTSCColor(bwSnap, nil)
page1Snap := filterNTSCColor(snapshotHiResModeMono(a, false /*2nd page*/, false /*mix*/, color.White), nil) // HGR 1
page2Snap := filterNTSCColor(snapshotHiResModeMono(a, true /*2nd page*/, false /*mix*/, color.White), nil) // HGR 2
size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2)
out := image.NewRGBA(size)

View File

@ -9,15 +9,21 @@ const (
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"
height := hiResHeight
if mixedMode {
height = hiResHeightMixed
}
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)
for y := 0; y < height; y++ {
lineParts := [][]uint8{
@ -27,21 +33,34 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, l
x := 0
// 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)
if getNTSCMask {
ntscMask.Set(x, y, color.Opaque)
}
x++
for iByte := 0; iByte < hiResLineBytes; iByte++ {
for iPart := 0; iPart < 2; iPart++ {
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++ {
// Set color
bit := (b >> j) & 1
colour := light
if bit == 0 {
colour = color.Black
}
img.Set(x, y, colour)
// Set mask if requested
if getNTSCMask {
ntscMask.Set(x, y, mask)
}
x++
}
}
}
}
return img
return img, ntscMask
}

View File

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

View File

@ -7,7 +7,7 @@ const (
ioFlagMixed uint8 = 0x52
ioFlagSecondPage uint8 = 0x54
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
ioFlagAnnunciator2 uint8 = 0x5c
ioFlagAnnunciator3 uint8 = 0x5e
@ -20,6 +20,9 @@ const (
ioDataPaddle1 uint8 = 0x65
ioDataPaddle2 uint8 = 0x66
ioDataPaddle3 uint8 = 0x67
ioFlag1RGBCard uint8 = 0x7e // Not real softSwitches. Using the numbers to store the flags somewhere.
ioFlag2RGBCard uint8 = 0x7f
)
func addApple2SoftSwitches(io *ioC0Page) {
@ -71,6 +74,10 @@ func addApple2SoftSwitches(io *ioC0Page) {
io.addSoftSwitchR(0x6F, getPaddleSoftSwitch(3), "PDL3")
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 {
@ -141,9 +148,9 @@ func getButtonSoftSwitch(i int) softSwitchR {
/*
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
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.
See: http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.06.html