mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-02-06 22:29:57 +00:00
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)
|
||||
- RGB for Super High Resolution and RGB card
|
||||
- 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:
|
||||
- Sound
|
||||
- Joystick support. Up to two joysticks or four paddles
|
||||
@ -131,10 +132,10 @@ Line:
|
||||
- F5: Toggle speed between real and fastest
|
||||
- Ctrl-F5: Show current speed in Mhz
|
||||
- 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)
|
||||
- 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
|
||||
- F12: Save a screen snapshot to a file `snapshot.png`
|
||||
- Pause: Pause the emulation
|
||||
|
@ -80,9 +80,10 @@ func SDLRun(a *apple2.Apple2) {
|
||||
if !a.IsPaused() {
|
||||
var img *image.RGBA
|
||||
if kp.showPages {
|
||||
img = apple2.SnapshotHGRModes(a)
|
||||
img = a.SnapshotParts()
|
||||
window.SetTitle(a.Name + " " + a.VideoModeName())
|
||||
} else {
|
||||
img = apple2.Snapshot(a)
|
||||
img = a.Snapshot()
|
||||
}
|
||||
if img != nil {
|
||||
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
|
||||
*/
|
||||
|
||||
// Snapshot the currently visible screen
|
||||
func Snapshot(a *Apple2) *image.RGBA {
|
||||
return activeSnapshot(a, false)
|
||||
}
|
||||
const (
|
||||
videoText40 uint8 = 0x01
|
||||
videoGR uint8 = 0x02
|
||||
videoHGR uint8 = 0x03
|
||||
|
||||
func activeSnapshot(a *Apple2, raw bool) *image.RGBA {
|
||||
isColor := a.isColor
|
||||
videoText80 uint8 = 0x08
|
||||
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)
|
||||
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||
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
|
||||
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
|
||||
if isColor {
|
||||
if a.isColor {
|
||||
lightColor = color.White
|
||||
} else {
|
||||
// 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 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)
|
||||
} else if isTextMode {
|
||||
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)
|
||||
}
|
||||
applyNTSCFilter = false
|
||||
}
|
||||
|
||||
if !raw && !isSuperHighResMode {
|
||||
snap = linesSeparatedFilter(snap)
|
||||
if isMixMode {
|
||||
isMix80 := (videoMode & videoMixText80) != 0
|
||||
bottom := snapshotTextMode(a, isMix80, isSecondPage, lightColor)
|
||||
snap = mixSnapshots(snap, bottom)
|
||||
}
|
||||
|
||||
if applyNTSCFilter {
|
||||
snap = filterNTSCColor(snap, ntscMask)
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
// 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(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
|
||||
// Snapshot the currently visible screen
|
||||
func (a *Apple2) Snapshot() *image.RGBA {
|
||||
videoMode := getCurrentVideoMode(a)
|
||||
snap := snapshotByMode(a, videoMode)
|
||||
|
||||
size := image.Rect(0, 0, hiResWidth*4, hiResHeight*2)
|
||||
out := image.NewRGBA(size)
|
||||
|
||||
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))
|
||||
}
|
||||
if snap.Bounds().Dy() == hiResHeight {
|
||||
// Apply the filter to regular CRT snapshots with 192 lines. Not to SHR
|
||||
snap = linesSeparatedFilter(snap)
|
||||
}
|
||||
|
||||
return out
|
||||
return snap
|
||||
}
|
||||
|
||||
func mixSnapshots(top, bottom *image.RGBA) *image.RGBA {
|
||||
topBounds := top.Bounds()
|
||||
topWidth := topBounds.Dx()
|
||||
topHeight := topBounds.Dy()
|
||||
|
||||
bottomBounds := bottom.Bounds()
|
||||
bottomWidth := bottomBounds.Dx()
|
||||
bottomHeight := bottomBounds.Dy()
|
||||
|
||||
topWidth := top.Bounds().Dx()
|
||||
bottomWidth := bottom.Bounds().Dx()
|
||||
factor := topWidth / bottomWidth
|
||||
|
||||
size := image.Rect(0, 0, topWidth, topHeight+bottomHeight)
|
||||
out := image.NewRGBA(size)
|
||||
|
||||
// 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++ {
|
||||
// Copy bottom's bottom on top's bottom, applying the factor
|
||||
for y := hiResHeightMixed; y < hiResHeight; y++ {
|
||||
for x := 0; x < topWidth; x++ {
|
||||
c := bottom.At(x, y)
|
||||
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
|
||||
func SaveSnapshot(a *Apple2, filename string) error {
|
||||
img := Snapshot(a)
|
||||
img := a.Snapshot()
|
||||
img = squarishPixelsFilter(img)
|
||||
|
||||
f, err := os.Create(filename)
|
||||
@ -184,17 +224,3 @@ func linesSeparatedFilter(in *image.RGBA) *image.RGBA {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
120
screenDebugParts.go
Normal file
120
screenDebugParts.go
Normal file
@ -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
|
||||
)
|
||||
|
||||
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"
|
||||
height := hiResHeight
|
||||
if mixedMode {
|
||||
height = hiResHeightMixed
|
||||
}
|
||||
size := image.Rect(0, 0, doubleHiResWidth, height)
|
||||
size := image.Rect(0, 0, doubleHiResWidth, hiResHeight)
|
||||
img := image.NewRGBA(size)
|
||||
|
||||
// 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
|
||||
var ntscMask *image.Alpha
|
||||
if getNTSCMask {
|
||||
ntscMask = image.NewAlpha(size)
|
||||
}
|
||||
|
||||
img := image.NewRGBA(size)
|
||||
for y := 0; y < height; y++ {
|
||||
for y := 0; y < hiResHeight; y++ {
|
||||
lineParts := [][]uint8{
|
||||
getHiResLine(a, y, isSecondPage, true),
|
||||
getHiResLine(a, y, isSecondPage, false),
|
||||
getHiResLine(a, y, isSecondPage, true /*auxmem*/),
|
||||
getHiResLine(a, y, isSecondPage, false /*auxmem*/),
|
||||
}
|
||||
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)
|
||||
if getNTSCMask {
|
||||
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 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
|
||||
|
@ -19,9 +19,9 @@ func getHiResLineOffset(line int) uint16 {
|
||||
// See "Understanding the Apple II", page 5-14
|
||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||
section := line >> 6 // Top, middle and bottom
|
||||
outerEigth := (line >> 3) & 0x07
|
||||
innerEigth := line & 0x07
|
||||
return uint16(section*40 + outerEigth*0x80 + innerEigth*0x400)
|
||||
outerEighth := (line >> 3) & 0x07
|
||||
innerEighth := line & 0x07
|
||||
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
height := hiResHeight
|
||||
if mixedMode {
|
||||
height = hiResHeightMixed
|
||||
}
|
||||
|
||||
size := image.Rect(0, 0, 2*hiResWidth, height)
|
||||
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
|
||||
img := image.NewRGBA(size)
|
||||
|
||||
for y := 0; y < height; y++ {
|
||||
for y := 0; y < hiResHeight; y++ {
|
||||
bytes := getHiResLine(a, y, isSecondPage, false /*auxMem*/)
|
||||
x := 0
|
||||
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 {
|
||||
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage, false)
|
||||
if isMixMode {
|
||||
lines -= textLinesMix
|
||||
}
|
||||
func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
text, columns, lines := getActiveText(a, isDoubleResMode, isSecondPage)
|
||||
grLines := lines * 2
|
||||
pixelWidth := loResPixelWidth
|
||||
if isDoubleResMode {
|
||||
@ -61,7 +58,7 @@ func snapshotLoResModeMono(a *Apple2, isDoubleResMode bool, isSecondPage bool, i
|
||||
grPixel = char & 0xf
|
||||
}
|
||||
// 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
|
||||
|
||||
if isDoubleResMode && ((c % 2) == 0) {
|
||||
|
@ -9,11 +9,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
charWidth = 7
|
||||
charHeight = 8
|
||||
textColumns = 40
|
||||
textLines = 24
|
||||
textLinesMix = 4
|
||||
charWidth = 7
|
||||
charHeight = 8
|
||||
textColumns = 40
|
||||
textLines = 24
|
||||
|
||||
textPage1Address = uint16(0x0400)
|
||||
textPage2Address = uint16(0x0800)
|
||||
textPageSize = uint16(0x0400)
|
||||
@ -24,44 +24,33 @@ func getTextCharOffset(col int, line int) uint16 {
|
||||
// See "Understanding the Apple II", page 5-10
|
||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
||||
section := line / 8 // Top, middle and bottom
|
||||
eigth := line % 8
|
||||
return uint16(section*40 + eigth*0x80 + col)
|
||||
eighth := line % 8
|
||||
return uint16(section*40 + eighth*0x80 + col)
|
||||
}
|
||||
|
||||
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool, light color.Color) *image.RGBA {
|
||||
text, columns, lines := getActiveText(a, is80Columns, isSecondPage, isMixMode)
|
||||
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, light color.Color) *image.RGBA {
|
||||
text, columns, lines := getActiveText(a, is80Columns, isSecondPage)
|
||||
return renderTextMode(a, text, columns, lines, light)
|
||||
}
|
||||
|
||||
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool) ([]uint8, int, int) {
|
||||
|
||||
lines := textLines
|
||||
if isMixMode {
|
||||
lines = textLinesMix
|
||||
}
|
||||
|
||||
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool) ([]uint8, int, int) {
|
||||
if !is80Columns {
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
||||
return text40Columns, textColumns, lines
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
return text40Columns, textColumns, textLines
|
||||
}
|
||||
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
||||
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage, isMixMode)
|
||||
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage)
|
||||
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage)
|
||||
// Merge the two 40 cols to return 80 cols
|
||||
text80Columns := make([]uint8, 2*len(text40Columns))
|
||||
for i := 0; i < len(text40Columns); i++ {
|
||||
text80Columns[2*i] = text40ColumnsAlt[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 {
|
||||
lineStart := 0
|
||||
if isMixMode {
|
||||
lineStart = textLines - textLinesMix
|
||||
}
|
||||
|
||||
func getTextFromMemory(mem *memoryRange, isSecondPage bool) []uint8 {
|
||||
addressStart := textPage1Address
|
||||
if isSecondPage {
|
||||
addressStart = textPage2Address
|
||||
@ -69,11 +58,10 @@ func getTextFromMemory(mem *memoryRange, isSecondPage bool, isMixMode bool) []ui
|
||||
addressEnd := addressStart + textPageSize
|
||||
data := mem.subRange(addressStart, addressEnd)
|
||||
|
||||
lines := textLines - lineStart
|
||||
text := make([]uint8, lines*textColumns)
|
||||
for l := 0; l < lines; l++ {
|
||||
text := make([]uint8, textLines*textColumns)
|
||||
for l := 0; l < textLines; l++ {
|
||||
for c := 0; c < textColumns; c++ {
|
||||
char := data[getTextCharOffset(c, l+lineStart)]
|
||||
char := data[getTextCharOffset(c, l)]
|
||||
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 {
|
||||
// Flash mode is 2Hz
|
||||
// Flash mode is 2Hz (host time)
|
||||
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
||||
|
||||
width := columns * charWidth
|
||||
@ -137,7 +125,7 @@ func DumpTextModeAnsi(a *Apple2) string {
|
||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||
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 += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user