Screen video modes refactor. Video debug mode
This commit is contained in:
parent
caabdf4781
commit
aa198877ec
|
@ -42,6 +42,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||||
- 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 and RGB card
|
- RGB for Super High Resolution and RGB card
|
||||||
- ANSI Console, avoiding the SDL2 dependency
|
- ANSI Console, avoiding the SDL2 dependency
|
||||||
|
- Debug mode: shows four panels with actual screen, page1, page2 and extra info dependant of the video mode
|
||||||
- Other features:
|
- Other features:
|
||||||
- Sound
|
- Sound
|
||||||
- Joystick support. Up to two joysticks or four paddles
|
- Joystick support. Up to two joysticks or four paddles
|
||||||
|
@ -131,10 +132,10 @@ Line:
|
||||||
- F5: Toggle speed between real and fastest
|
- F5: Toggle speed between real and fastest
|
||||||
- Ctrl-F5: Show current speed in Mhz
|
- Ctrl-F5: Show current speed in Mhz
|
||||||
- F6: Toggle between NTSC color TV and green phosphor monochrome monitor
|
- F6: Toggle between NTSC color TV and green phosphor monochrome monitor
|
||||||
- Ctrl-F6: Show a split screen with the views for NTSC color TV, monochrome monitor, HGR page 1 and HGR page 2.
|
- Ctrl-F6: Show the video mode and a split screen with the views for NTSC color TV, page 1, page 2 and extra info.
|
||||||
- F7: Save current state to disk (incomplete)
|
- F7: Save current state to disk (incomplete)
|
||||||
- F8: Restore state from disk (incomplete)
|
- F8: Restore state from disk (incomplete)
|
||||||
- F10: Cycle character generator codepages. Only if the character generator ROM has more than one 2Kb page.
|
- F10: Cycle character generator code pages. Only if the character generator ROM has more than one 2Kb page.
|
||||||
- F11: Toggle on and off the trace to console of the CPU execution
|
- F11: Toggle on and off the trace to console of the CPU execution
|
||||||
- F12: Save a screen snapshot to a file `snapshot.png`
|
- F12: Save a screen snapshot to a file `snapshot.png`
|
||||||
- Pause: Pause the emulation
|
- Pause: Pause the emulation
|
||||||
|
|
|
@ -80,9 +80,10 @@ func SDLRun(a *apple2.Apple2) {
|
||||||
if !a.IsPaused() {
|
if !a.IsPaused() {
|
||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
if kp.showPages {
|
if kp.showPages {
|
||||||
img = apple2.SnapshotHGRModes(a)
|
img = a.SnapshotParts()
|
||||||
|
window.SetTitle(a.Name + " " + a.VideoModeName())
|
||||||
} else {
|
} else {
|
||||||
img = apple2.Snapshot(a)
|
img = a.Snapshot()
|
||||||
}
|
}
|
||||||
if img != nil {
|
if img != nil {
|
||||||
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
||||||
|
|
208
screen.go
208
screen.go
|
@ -15,28 +15,89 @@ References:
|
||||||
- "More Colors for your Apple", https://archive.org/details/byte-magazine-1979-06/page/n61
|
- "More Colors for your Apple", https://archive.org/details/byte-magazine-1979-06/page/n61
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Snapshot the currently visible screen
|
const (
|
||||||
func Snapshot(a *Apple2) *image.RGBA {
|
videoText40 uint8 = 0x01
|
||||||
return activeSnapshot(a, false)
|
videoGR uint8 = 0x02
|
||||||
}
|
videoHGR uint8 = 0x03
|
||||||
|
|
||||||
func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
videoText80 uint8 = 0x08
|
||||||
isColor := a.isColor
|
videoDGR uint8 = 0x09
|
||||||
|
videoDHGR uint8 = 0x0a
|
||||||
|
|
||||||
|
videoMono560 uint8 = 0x10
|
||||||
|
videoRGBMix uint8 = 0x11
|
||||||
|
videoSHR uint8 = 0x12
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
videoBaseMask uint8 = 0x1f
|
||||||
|
videoSecondPage uint8 = 0x20
|
||||||
|
videoMixText40 uint8 = 0x40
|
||||||
|
videoMixText80 uint8 = 0x80
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCurrentVideoMode(a *Apple2) uint8 {
|
||||||
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
||||||
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
|
||||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
|
||||||
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
||||||
|
|
||||||
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
||||||
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
||||||
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
||||||
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
||||||
|
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
||||||
|
|
||||||
|
mode := uint8(0)
|
||||||
|
if isSuperHighResMode {
|
||||||
|
mode = videoSHR
|
||||||
|
isMixMode = false
|
||||||
|
} else if isTextMode {
|
||||||
|
if is80Columns {
|
||||||
|
mode = videoText80
|
||||||
|
} else {
|
||||||
|
mode = videoText40
|
||||||
|
}
|
||||||
|
isMixMode = false
|
||||||
|
} else if isHiResMode {
|
||||||
|
if !isDoubleResMode {
|
||||||
|
mode = videoHGR
|
||||||
|
} else if isMono560 {
|
||||||
|
mode = videoMono560
|
||||||
|
} else if isRGBMixMode {
|
||||||
|
mode = videoRGBMix
|
||||||
|
} else {
|
||||||
|
mode = videoDHGR
|
||||||
|
}
|
||||||
|
} else if isDoubleResMode {
|
||||||
|
mode = videoDGR
|
||||||
|
} else {
|
||||||
|
mode = videoGR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifiers
|
||||||
|
if isMixMode {
|
||||||
|
if is80Columns {
|
||||||
|
mode |= videoMixText80
|
||||||
|
} else {
|
||||||
|
mode |= videoMixText40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
|
if isSecondPage {
|
||||||
|
mode |= videoSecondPage
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapshotByMode(a *Apple2, videoMode uint8) *image.RGBA {
|
||||||
|
videoBase := videoMode & videoBaseMask
|
||||||
|
isSecondPage := (videoMode & videoSecondPage) != 0
|
||||||
|
isMixMode := (videoMode & (videoMixText40 | videoMixText80)) != 0
|
||||||
|
|
||||||
var lightColor color.Color
|
var lightColor color.Color
|
||||||
if isColor {
|
if a.isColor {
|
||||||
lightColor = color.White
|
lightColor = color.White
|
||||||
} else {
|
} else {
|
||||||
// Color for typical Apple ][ period green P1 phosphor monitors
|
// Color for typical Apple ][ period green P1 phosphor monitors
|
||||||
|
@ -45,101 +106,80 @@ func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyNTSCFilter := a.isColor
|
||||||
var snap *image.RGBA
|
var snap *image.RGBA
|
||||||
var ntscMask *image.Alpha
|
var ntscMask *image.Alpha
|
||||||
if isSuperHighResMode { // Has to be first and disables the rest
|
switch videoBase {
|
||||||
|
case videoText40:
|
||||||
|
snap = snapshotTextMode(a, false /*is80Columns*/, isSecondPage, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoText80:
|
||||||
|
snap = snapshotTextMode(a, true /*is80Columns*/, isSecondPage, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoGR:
|
||||||
|
snap = snapshotLoResModeMono(a, false /*isDoubleResMode*/, isSecondPage, lightColor)
|
||||||
|
case videoDGR:
|
||||||
|
snap = snapshotLoResModeMono(a, true /*isDoubleResMode*/, isSecondPage, lightColor)
|
||||||
|
case videoHGR:
|
||||||
|
snap = snapshotHiResModeMono(a, isSecondPage, lightColor)
|
||||||
|
case videoDHGR:
|
||||||
|
snap, _ = snapshotDoubleHiResModeMono(a, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
||||||
|
case videoMono560:
|
||||||
|
snap, _ = snapshotDoubleHiResModeMono(a, isSecondPage, false /*isRGBMixMode*/, lightColor)
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoRGBMix:
|
||||||
|
snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/, lightColor)
|
||||||
|
case videoSHR:
|
||||||
snap = snapshotSuperHiResMode(a)
|
snap = snapshotSuperHiResMode(a)
|
||||||
} else if isTextMode {
|
applyNTSCFilter = false
|
||||||
snap = snapshotTextMode(a, is80Columns, isSecondPage, false /*isMixMode*/, lightColor)
|
|
||||||
} else {
|
|
||||||
if isHiResMode {
|
|
||||||
if isDoubleResMode {
|
|
||||||
snap, ntscMask = snapshotDoubleHiResModeMono(a, isSecondPage, isMixMode, isRGBMixMode, lightColor)
|
|
||||||
} else {
|
|
||||||
snap = snapshotHiResModeMono(a, isSecondPage, isMixMode, lightColor)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snap = snapshotLoResModeMono(a, isDoubleResMode, isSecondPage, isMixMode, lightColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isMixMode {
|
|
||||||
snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor)
|
|
||||||
snap = mixSnapshots(snap, snapText)
|
|
||||||
}
|
|
||||||
if isColor && !(raw || isMono560) {
|
|
||||||
snap = filterNTSCColor(snap, ntscMask)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !raw && !isSuperHighResMode {
|
if isMixMode {
|
||||||
snap = linesSeparatedFilter(snap)
|
isMix80 := (videoMode & videoMixText80) != 0
|
||||||
|
bottom := snapshotTextMode(a, isMix80, isSecondPage, lightColor)
|
||||||
|
snap = mixSnapshots(snap, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if applyNTSCFilter {
|
||||||
|
snap = filterNTSCColor(snap, ntscMask)
|
||||||
}
|
}
|
||||||
return snap
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnapshotHGRModes to get all modes mixed
|
// Snapshot the currently visible screen
|
||||||
func SnapshotHGRModes(a *Apple2) *image.RGBA {
|
func (a *Apple2) Snapshot() *image.RGBA {
|
||||||
bwSnap := activeSnapshot(a, true)
|
videoMode := getCurrentVideoMode(a)
|
||||||
if bwSnap.Bounds().Dx() == hiResWidth {
|
snap := snapshotByMode(a, videoMode)
|
||||||
bwSnap = doubleWidthFilter(bwSnap)
|
|
||||||
}
|
|
||||||
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)
|
if snap.Bounds().Dy() == hiResHeight {
|
||||||
out := image.NewRGBA(size)
|
// Apply the filter to regular CRT snapshots with 192 lines. Not to SHR
|
||||||
|
snap = linesSeparatedFilter(snap)
|
||||||
for y := 0; y < hiResHeight; y++ {
|
|
||||||
for x := 0; x < hiResWidth*2; x++ {
|
|
||||||
out.Set(x, y, colorSnap.At(x, y))
|
|
||||||
out.Set(x+hiResWidth*2, y, bwSnap.At(x, y))
|
|
||||||
out.Set(x, y+hiResHeight, page1Snap.At(x, y))
|
|
||||||
out.Set(x+hiResWidth*2, y+hiResHeight, page2Snap.At(x, y))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
||||||
topBounds := top.Bounds()
|
topWidth := top.Bounds().Dx()
|
||||||
topWidth := topBounds.Dx()
|
bottomWidth := bottom.Bounds().Dx()
|
||||||
topHeight := topBounds.Dy()
|
|
||||||
|
|
||||||
bottomBounds := bottom.Bounds()
|
|
||||||
bottomWidth := bottomBounds.Dx()
|
|
||||||
bottomHeight := bottomBounds.Dy()
|
|
||||||
|
|
||||||
factor := topWidth / bottomWidth
|
factor := topWidth / bottomWidth
|
||||||
|
|
||||||
size := image.Rect(0, 0, topWidth, topHeight+bottomHeight)
|
// Copy bottom's bottom on top's bottom, applying the factor
|
||||||
out := image.NewRGBA(size)
|
for y := hiResHeightMixed; y < hiResHeight; y++ {
|
||||||
|
for x := 0; x < topWidth; x++ {
|
||||||
// Copy top
|
|
||||||
for y := topBounds.Min.Y; y < topBounds.Max.Y; y++ {
|
|
||||||
for x := topBounds.Min.X; x < topBounds.Max.X; x++ {
|
|
||||||
c := top.At(x, y)
|
|
||||||
out.Set(x, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy bottom, applying the factor
|
|
||||||
for y := bottomBounds.Min.Y; y < bottomBounds.Max.Y; y++ {
|
|
||||||
for x := bottomBounds.Min.X; x < bottomBounds.Max.X; x++ {
|
|
||||||
c := bottom.At(x, y)
|
c := bottom.At(x, y)
|
||||||
for f := 0; f < factor; f++ {
|
for f := 0; f < factor; f++ {
|
||||||
out.Set(x*factor+f, topHeight+y, c)
|
top.Set(x*factor+f, y, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return top
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveSnapshot saves a snapshot of the screen to a png file
|
// SaveSnapshot saves a snapshot of the screen to a png file
|
||||||
func SaveSnapshot(a *Apple2, filename string) error {
|
func SaveSnapshot(a *Apple2, filename string) error {
|
||||||
img := Snapshot(a)
|
img := a.Snapshot()
|
||||||
img = squarishPixelsFilter(img)
|
img = squarishPixelsFilter(img)
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
|
@ -184,17 +224,3 @@ func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func doubleWidthFilter(in *image.RGBA) *image.RGBA {
|
|
||||||
b := in.Bounds()
|
|
||||||
size := image.Rect(0, 0, 2*b.Dx(), b.Dy())
|
|
||||||
out := image.NewRGBA(size)
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
c := in.At(x, y)
|
|
||||||
out.Set(2*x, y, c)
|
|
||||||
out.Set(2*x+1, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package apple2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SnapshotParts the currently visible screen
|
||||||
|
func (a *Apple2) SnapshotParts() *image.RGBA {
|
||||||
|
videoMode := getCurrentVideoMode(a)
|
||||||
|
snapScreen := snapshotByMode(a, videoMode)
|
||||||
|
snapPage1 := snapshotByMode(a, videoMode&^videoSecondPage)
|
||||||
|
snapPage2 := snapshotByMode(a, videoMode|videoSecondPage)
|
||||||
|
var snapAux *image.RGBA
|
||||||
|
|
||||||
|
/*videoBase := videoMode & videoBaseMask
|
||||||
|
if videoBase == videoRGBMix {
|
||||||
|
isSecondPage := (videoMode & videoSecondPage) != 0
|
||||||
|
_, mask := snapshotDoubleHiResModeMono(a, isSecondPage, true /*isRGBMixMode*/ /*, color.White)
|
||||||
|
snapAux = filterMask(mask)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if snapAux == nil && (videoMode&videoMixText80) != 0 {
|
||||||
|
snapAux = snapshotByMode(a, videoText80)
|
||||||
|
}
|
||||||
|
|
||||||
|
if snapAux == nil {
|
||||||
|
snapAux = snapshotByMode(a, videoText40)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixFourSnapshots([]*image.RGBA{snapScreen, snapAux, snapPage1, snapPage2})
|
||||||
|
}
|
||||||
|
|
||||||
|
// VideoModeName returns the name of the current video mode
|
||||||
|
func (a *Apple2) VideoModeName() string {
|
||||||
|
videoMode := getCurrentVideoMode(a)
|
||||||
|
videoBase := videoMode & videoBaseMask
|
||||||
|
|
||||||
|
var name string
|
||||||
|
applyNTSCFilter := a.isColor
|
||||||
|
|
||||||
|
switch videoBase {
|
||||||
|
case videoText40:
|
||||||
|
name = "TEXT40COL"
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoText80:
|
||||||
|
name = "TEXT80COL"
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoGR:
|
||||||
|
name = "GR"
|
||||||
|
case videoDGR:
|
||||||
|
name = "DGR"
|
||||||
|
case videoHGR:
|
||||||
|
name = "HGR"
|
||||||
|
case videoDHGR:
|
||||||
|
name = "DHGR"
|
||||||
|
case videoMono560:
|
||||||
|
name = "Mono560"
|
||||||
|
applyNTSCFilter = false
|
||||||
|
case videoRGBMix:
|
||||||
|
name = "RGMMIX"
|
||||||
|
case videoSHR:
|
||||||
|
name = "SHR"
|
||||||
|
applyNTSCFilter = false
|
||||||
|
default:
|
||||||
|
name = "Unknown video mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoMode & videoSecondPage) != 0 {
|
||||||
|
name += "-PAGE2"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoMode & videoMixText40) != 0 {
|
||||||
|
name += "-MIX40"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoMode & videoMixText80) != 0 {
|
||||||
|
name += "-MIX80"
|
||||||
|
}
|
||||||
|
|
||||||
|
if applyNTSCFilter {
|
||||||
|
name += "-NTSC"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func mixFourSnapshots(snaps []*image.RGBA) *image.RGBA {
|
||||||
|
size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2)
|
||||||
|
out := image.NewRGBA(size)
|
||||||
|
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if snaps[i].Bounds().Dx() < hiResWidth*2 {
|
||||||
|
snaps[i] = doubleWidthFilter(snaps[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := 0; y < hiResHeight; y++ {
|
||||||
|
for x := 0; x < hiResWidth*2; x++ {
|
||||||
|
out.Set(x, y, snaps[0].At(x, y))
|
||||||
|
out.Set(x+hiResWidth*2, y, snaps[1].At(x, y))
|
||||||
|
out.Set(x, y+hiResHeight, snaps[2].At(x, y))
|
||||||
|
out.Set(x+hiResWidth*2, y+hiResHeight, snaps[3].At(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleWidthFilter(in *image.RGBA) *image.RGBA {
|
||||||
|
b := in.Bounds()
|
||||||
|
size := image.Rect(0, 0, 2*b.Dx(), b.Dy())
|
||||||
|
out := image.NewRGBA(size)
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
c := in.At(x, y)
|
||||||
|
out.Set(2*x, y, c)
|
||||||
|
out.Set(2*x+1, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
|
@ -9,29 +9,25 @@ const (
|
||||||
doubleHiResWidth = 2 * hiResWidth
|
doubleHiResWidth = 2 * hiResWidth
|
||||||
)
|
)
|
||||||
|
|
||||||
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, getNTSCMask bool, light color.Color) (*image.RGBA, *image.Alpha) {
|
func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage 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
|
size := image.Rect(0, 0, doubleHiResWidth, hiResHeight)
|
||||||
if mixedMode {
|
img := image.NewRGBA(size)
|
||||||
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
|
// To support RGB-mode 14 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
|
// See: https://apple2online.com/web_documents/Video-7%20Manual%20KB.pdf
|
||||||
var ntscMask *image.Alpha
|
var ntscMask *image.Alpha
|
||||||
if getNTSCMask {
|
if getNTSCMask {
|
||||||
ntscMask = image.NewAlpha(size)
|
ntscMask = image.NewAlpha(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
img := image.NewRGBA(size)
|
for y := 0; y < hiResHeight; y++ {
|
||||||
for y := 0; y < height; y++ {
|
|
||||||
lineParts := [][]uint8{
|
lineParts := [][]uint8{
|
||||||
getHiResLine(a, y, isSecondPage, true),
|
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
|
||||||
getHiResLine(a, y, isSecondPage, false),
|
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
|
||||||
}
|
}
|
||||||
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 {
|
if getNTSCMask {
|
||||||
ntscMask.Set(x, y, color.Opaque)
|
ntscMask.Set(x, y, color.Opaque)
|
||||||
|
@ -40,10 +36,12 @@ func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, g
|
||||||
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
|
mask := color.Transparent // Apply the NTSC filter
|
||||||
if getNTSCMask && b&0x80 == 0 {
|
if getNTSCMask && b&0x80 == 0 {
|
||||||
mask = color.Opaque // Do not apply the NTSC filter
|
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
|
// Set color
|
||||||
bit := (b >> j) & 1
|
bit := (b >> j) & 1
|
||||||
|
|
|
@ -19,9 +19,9 @@ func getHiResLineOffset(line int) uint16 {
|
||||||
// See "Understanding the Apple II", page 5-14
|
// See "Understanding the Apple II", page 5-14
|
||||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||||
section := line >> 6 // Top, middle and bottom
|
section := line >> 6 // Top, middle and bottom
|
||||||
outerEigth := (line >> 3) & 0x07
|
outerEighth := (line >> 3) & 0x07
|
||||||
innerEigth := line & 0x07
|
innerEighth := line & 0x07
|
||||||
return uint16(section*40 + outerEigth*0x80 + innerEigth*0x400)
|
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 {
|
func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 {
|
||||||
|
@ -34,18 +34,12 @@ func getHiResLine(a *Apple2, line int, isSecondPage bool, auxMem bool) []uint8 {
|
||||||
return a.mmu.getPhysicalMainRAM(auxMem).subRange(address, address+hiResLineBytes)
|
return a.mmu.getPhysicalMainRAM(auxMem).subRange(address, address+hiResLineBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light color.Color) *image.RGBA {
|
func snapshotHiResModeMono(a *Apple2, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
// As described in "Undertanding the Apple II", with half pixel shifts
|
// As described in "Undertanding the Apple II", with half pixel shifts
|
||||||
|
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
|
||||||
height := hiResHeight
|
|
||||||
if mixedMode {
|
|
||||||
height = hiResHeightMixed
|
|
||||||
}
|
|
||||||
|
|
||||||
size := image.Rect(0, 0, 2*hiResWidth, height)
|
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
for y := 0; y < height; y++ {
|
for y := 0; y < hiResHeight; y++ {
|
||||||
bytes := getHiResLine(a, y, isSecondPage, false /*auxMem*/)
|
bytes := getHiResLine(a, y, isSecondPage, false /*auxMem*/)
|
||||||
x := 0
|
x := 0
|
||||||
var previousColour color.Color = color.Black
|
var previousColour color.Color = color.Black
|
||||||
|
|
|
@ -38,11 +38,8 @@ func getColorPatterns(light color.Color) [16][16]color.Color {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, isMixMode bool, light color.Color) *image.RGBA {
|
func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage, false)
|
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage)
|
||||||
if isMixMode {
|
|
||||||
lines -= textLinesMix
|
|
||||||
}
|
|
||||||
grLines := lines * 2
|
grLines := lines * 2
|
||||||
pixelWidth := loResPixelWidth
|
pixelWidth := loResPixelWidth
|
||||||
if isDoubleResMode {
|
if isDoubleResMode {
|
||||||
|
@ -61,7 +58,7 @@ func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, i
|
||||||
grPixel = char & 0xf
|
grPixel = char & 0xf
|
||||||
}
|
}
|
||||||
// We place pixelWidth mono pixels per graphic pixel.
|
// We place pixelWidth mono pixels per graphic pixel.
|
||||||
// The groups of 4 mono pixels need to be alligned with an offset to get plain surfaces
|
// The groups of 4 mono pixels need to be aligned with an offset to get plain surfaces
|
||||||
offset := (c * pixelWidth) % 4
|
offset := (c * pixelWidth) % 4
|
||||||
|
|
||||||
if isDoubleResMode && ((c % 2) == 0) {
|
if isDoubleResMode && ((c % 2) == 0) {
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charWidth = 7
|
charWidth = 7
|
||||||
charHeight = 8
|
charHeight = 8
|
||||||
textColumns = 40
|
textColumns = 40
|
||||||
textLines = 24
|
textLines = 24
|
||||||
textLinesMix = 4
|
|
||||||
textPage1Address = uint16(0x0400)
|
textPage1Address = uint16(0x0400)
|
||||||
textPage2Address = uint16(0x0800)
|
textPage2Address = uint16(0x0800)
|
||||||
textPageSize = uint16(0x0400)
|
textPageSize = uint16(0x0400)
|
||||||
|
@ -24,44 +24,33 @@ 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
|
||||||
eigth := line % 8
|
eighth := line % 8
|
||||||
return uint16(section*40 + eigth*0x80 + col)
|
return uint16(section*40 + eighth*0x80 + col)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool, light color.Color) *image.RGBA {
|
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
text, columns, lines := getActiveText(a, is80Columns, isSecondPage, isMixMode)
|
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
|
||||||
return renderTextMode(a, text, columns, lines, light)
|
return renderTextMode(a, text, columns, lines, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool) ([]uint8, int, int) {
|
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool) ([]uint8, int, int) {
|
||||||
|
|
||||||
lines := textLines
|
|
||||||
if isMixMode {
|
|
||||||
lines = textLinesMix
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is80Columns {
|
if !is80Columns {
|
||||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||||
return text40Columns, textColumns, lines
|
return text40Columns, textColumns, textLines
|
||||||
}
|
}
|
||||||
|
|
||||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||||
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage, isMixMode)
|
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, lines
|
return text80Columns, textColumns * 2, textLines
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextFromMemory(mem *memoryRange, isSecondPage bool, isMixMode bool) []uint8 {
|
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
||||||
lineStart := 0
|
|
||||||
if isMixMode {
|
|
||||||
lineStart = textLines - textLinesMix
|
|
||||||
}
|
|
||||||
|
|
||||||
addressStart := textPage1Address
|
addressStart := textPage1Address
|
||||||
if isSecondPage {
|
if isSecondPage {
|
||||||
addressStart = textPage2Address
|
addressStart = textPage2Address
|
||||||
|
@ -69,11 +58,10 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool, isMixMode bool) []ui
|
||||||
addressEnd := addressStart + textPageSize
|
addressEnd := addressStart + textPageSize
|
||||||
data := mem.subRange(addressStart, addressEnd)
|
data := mem.subRange(addressStart, addressEnd)
|
||||||
|
|
||||||
lines := textLines - lineStart
|
text := make([]uint8, textLines*textColumns)
|
||||||
text := make([]uint8, lines*textColumns)
|
for l := 0; l < textLines; l++ {
|
||||||
for l := 0; l < lines; l++ {
|
|
||||||
for c := 0; c < textColumns; c++ {
|
for c := 0; c < textColumns; c++ {
|
||||||
char := data[getTextCharOffset(c, l+lineStart)]
|
char := data[getTextCharOffset(c, l)]
|
||||||
text[textColumns*l+c] = char
|
text[textColumns*l+c] = char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +69,7 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool, isMixMode bool) []ui
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color.Color) *image.RGBA {
|
func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color.Color) *image.RGBA {
|
||||||
// Flash mode is 2Hz
|
// Flash mode is 2Hz (host time)
|
||||||
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
||||||
|
|
||||||
width := columns * charWidth
|
width := columns * charWidth
|
||||||
|
@ -137,7 +125,7 @@ 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, false /*isMixedMode*/)
|
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
|
||||||
content := "\n"
|
content := "\n"
|
||||||
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue