Support monochrome graphics

This commit is contained in:
Ivan Izaguirre 2019-04-26 18:08:30 +02:00
parent d4d47f3968
commit 1196890c18
8 changed files with 150 additions and 39 deletions

View File

@ -85,6 +85,10 @@ func (p *ioC0Page) internalPeek(address uint8) uint8 {
return 0 return 0
} }
func (p *ioC0Page) all() []uint8 {
return nil
}
func (p *ioC0Page) Poke(address uint8, value uint8) { func (p *ioC0Page) Poke(address uint8, value uint8) {
//fmt.Printf("Poke on $C0%02x with %02x ", address, value) //fmt.Printf("Poke on $C0%02x with %02x ", address, value)
ss := p.softSwitchesW[address] ss := p.softSwitchesW[address]

View File

@ -20,6 +20,7 @@ type memoryPage interface {
Peek(uint8) uint8 Peek(uint8) uint8
Poke(uint8, uint8) Poke(uint8, uint8)
internalPeek(uint8) uint8 internalPeek(uint8) uint8
all() []uint8
} }
const ( const (
@ -43,6 +44,10 @@ func (mmu *memoryManager) internalPeek(address uint16) uint8 {
return mmu.activeMemory[hi].internalPeek(lo) return mmu.activeMemory[hi].internalPeek(lo)
} }
func (mmu *memoryManager) internalPage(hi uint8) []uint8 {
return mmu.activeMemory[hi].all()
}
// Poke sets the data at the given address // Poke sets the data at the given address
func (mmu *memoryManager) Poke(address uint16, value uint8) { func (mmu *memoryManager) Poke(address uint16, value uint8) {
if address == ioC8Off { if address == ioC8Off {

View File

@ -22,6 +22,10 @@ func (p *rxmPage) internalPeek(address uint8) uint8 {
return p.data[address] return p.data[address]
} }
func (p *rxmPage) all() []uint8 {
return p.data[:]
}
func (p *rxmPage) Poke(address uint8, value uint8) { func (p *rxmPage) Poke(address uint8, value uint8) {
p.touch(address, true) p.touch(address, true)
p.data[address] = value p.data[address] = value

View File

@ -10,18 +10,29 @@ import (
// Snapshot the currently visible screen // Snapshot the currently visible screen
func Snapshot(a *Apple2) *image.RGBA { func Snapshot(a *Apple2) *image.RGBA {
isTextMode := a.io.isSoftSwitchActive(ioFlagGraphics) isTextMode := a.io.isSoftSwitchActive(ioFlagText)
is80ColMode := a.io.isSoftSwitchActive(ioFlag80Col) is80ColMode := a.io.isSoftSwitchActive(ioFlag80Col)
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
pageIndex := 0 pageIndex := 0
if a.io.isSoftSwitchActive(ioFlagSecondPage) { if a.io.isSoftSwitchActive(ioFlagSecondPage) {
pageIndex = 1 pageIndex = 1
} }
if isTextMode && !is80ColMode { if isTextMode {
//Text mode if is80ColMode {
return snapshotTextMode(a, pageIndex) // Not supported
} else {
return snapshotTextMode(a, pageIndex)
}
} else {
if isHiResMode {
return snapshotGraphMode(a, pageIndex)
} else {
// Not supported
}
} }
fmt.Printf("t: %v, 8: %v\n", isTextMode, is80ColMode)
//fmt.Printf("g: %v, 8: %v, h: %v\n", isTextMode, is80ColMode, isHiResMode)
return nil return nil
//panic("Screen mode not supported") //panic("Screen mode not supported")
} }
@ -44,29 +55,39 @@ func saveSnapshot(a *Apple2) {
} }
const ( const (
charWidth = 7 charWidth = 7
charHeight = 8 charHeight = 8
textColumns = 40 textColumns = 40
textLines = 24 textLines = 24
textPage1Address = uint16(0x400) textPage1Address = uint16(0x0400)
textPage2Address = uint16(0x400) textPage2Address = uint16(0x0800)
graphWidth = 280
graphHeight = 192
graphPage1Address = uint16(0x2000)
graphPage2Address = uint16(0x4000)
) )
func getTextCharOffset(col int, line int) uint16 {
// See "Understand 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)
}
func getTextChar(a *Apple2, col int, line int, page int) uint8 { func getTextChar(a *Apple2, col int, line int, page int) uint8 {
address := textPage1Address address := textPage1Address
if page == 1 { if page == 1 {
address = textPage2Address address = textPage2Address
} }
address += getTextCharOffset(col, line)
// See "Understand the Apple II", page 5-10
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
section := line / 8 // Top, middle and bottom
eigth := line % 8
address += uint16(section*40 + eigth*0x80 + col)
return a.mmu.internalPeek(address) return a.mmu.internalPeek(address)
} }
func snapshotTextMode(a *Apple2, page int) *image.RGBA { func snapshotTextMode(a *Apple2, page int) *image.RGBA {
// TODO: Missing inverse and flash modes
width := textColumns * charWidth width := textColumns * charWidth
height := textLines * charHeight height := textLines * charHeight
size := image.Rect(0, 0, width, height) size := image.Rect(0, 0, width, height)
@ -90,3 +111,51 @@ func snapshotTextMode(a *Apple2, page int) *image.RGBA {
return img return img
} }
func getGraphLineOffset(line int) uint16 {
// See "Understand 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)
}
func getGraphLine(a *Apple2, line int, page int) []uint8 {
address := graphPage1Address
if page == 1 {
address = graphPage2Address
}
address += getGraphLineOffset(line)
hi := uint8(address >> 8)
lo := uint8(address)
memPage := a.mmu.internalPage(hi)
//fmt.Printf("line: %v, lo: %x\n", line, lo)
return memPage[lo : lo+40]
}
func snapshotGraphMode(a *Apple2, page int) *image.RGBA {
size := image.Rect(0, 0, graphWidth, graphHeight)
img := image.NewRGBA(size)
for y := 0; y < graphHeight; y++ {
bytes := getGraphLine(a, y, page)
x := 0
for _, b := range bytes {
for j := uint(0); j < 7; j++ {
bit := (b >> j) & 1
colour := color.Black
if bit == 1 {
colour = color.White
}
img.Set(x, y, colour)
x++
}
}
}
return img
}

24
apple2/screen_test.go Normal file
View File

@ -0,0 +1,24 @@
package apple2
import (
"testing"
)
func TestGetGraphLineOffest(t *testing.T) {
scenarios := map[int]uint16{
0: 0x2000,
1: 0x2400,
8: 0x2080,
63: 0x3f80,
64: 0x2028,
128: 0x2050,
191: 0x3fd0,
}
for in, want := range scenarios {
got := 0x2000 + getGraphLineOffset(in)
if want != got {
t.Errorf("expected %x but got %x for line %v", want, got, in)
}
}
}

View File

@ -3,7 +3,7 @@ package apple2
const ( const (
ioDataKeyboard uint8 = 0x10 ioDataKeyboard uint8 = 0x10
ioFlagGraphics uint8 = 0x50 ioFlagText uint8 = 0x50
ioFlagMixed uint8 = 0x52 ioFlagMixed uint8 = 0x52
ioFlagSecondPage uint8 = 0x54 ioFlagSecondPage uint8 = 0x54
ioFlagHiRes uint8 = 0x56 ioFlagHiRes uint8 = 0x56
@ -33,8 +33,8 @@ func addApple2SoftSwitches(io *ioC0Page) {
// for read and write. But the Apple2e take over some of them, with // for read and write. But the Apple2e take over some of them, with
// the prevention on acting only on writes. // the prevention on acting only on writes.
io.addSoftSwitchRW(0x50, getSoftSwitch(ioFlagGraphics, false)) io.addSoftSwitchRW(0x50, getSoftSwitch(ioFlagText, false))
io.addSoftSwitchRW(0x51, getSoftSwitch(ioFlagGraphics, true)) io.addSoftSwitchRW(0x51, getSoftSwitch(ioFlagText, true))
io.addSoftSwitchRW(0x52, getSoftSwitch(ioFlagMixed, false)) io.addSoftSwitchRW(0x52, getSoftSwitch(ioFlagMixed, false))
io.addSoftSwitchRW(0x53, getSoftSwitch(ioFlagMixed, true)) io.addSoftSwitchRW(0x53, getSoftSwitch(ioFlagMixed, true))
io.addSoftSwitchRW(0x54, getSoftSwitch(ioFlagSecondPage, false)) io.addSoftSwitchRW(0x54, getSoftSwitch(ioFlagSecondPage, false))

View File

@ -14,6 +14,10 @@ func (p *unassignedPage) internalPeek(address uint8) uint8 {
return 0xdd return 0xdd
} }
func (p *unassignedPage) all() []uint8 {
return nil
}
func (p *unassignedPage) Poke(address uint8, value uint8) { func (p *unassignedPage) Poke(address uint8, value uint8) {
//fmt.Printf("Write on address 0x%02x%02x\n", p.page, address) //fmt.Printf("Write on address 0x%02x%02x\n", p.page, address)
//panic(address) //panic(address)

View File

@ -42,26 +42,27 @@ func SDLRun(a *apple2.Apple2) {
} }
} }
img := *apple2.Snapshot(a) img := apple2.Snapshot(a)
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]), 40*7, 24*8, 32, 40*7*4, if img != nil {
0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff) surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]), 40*7, 24*8, 32, 40*7*4,
if err != nil { 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff)
panic(err) if err != nil {
panic(err)
}
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
panic(err)
}
renderer.Clear()
w, h := window.GetSize()
renderer.Copy(texture, nil, &sdl.Rect{X: 0, Y: 0, W: w, H: h})
renderer.Present()
surface.Free()
texture.Destroy()
} }
texture, err := renderer.CreateTextureFromSurface(surface)
if err != nil {
panic(err)
}
renderer.Clear()
w, h := window.GetSize()
renderer.Copy(texture, nil, &sdl.Rect{X: 0, Y: 0, W: w, H: h})
renderer.Present()
surface.Free()
texture.Destroy()
sdl.Delay(1000 / 60) sdl.Delay(1000 / 60)
} }