mirror of
https://github.com/freewilll/apple2-go.git
synced 2024-06-08 06:29:28 +00:00
Added comments to video.go
This commit is contained in:
parent
650669ecff
commit
ce69239c1e
|
@ -38,6 +38,7 @@ Download `apple2e.rom` from
|
||||||
* ctrl-alt-R reset
|
* ctrl-alt-R reset
|
||||||
* ctrl-alt-M mute
|
* ctrl-alt-M mute
|
||||||
* ctrl-alt-C caps lock
|
* ctrl-alt-C caps lock
|
||||||
|
* ctrl-alt-F show FPS
|
||||||
|
|
||||||
## Running the tests
|
## Running the tests
|
||||||
### Setup
|
### Setup
|
||||||
|
|
89
apple2.go
89
apple2.go
|
@ -1,10 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
// Main emulator executable. This integrates all the components of the emulator
|
||||||
|
// and contains the ebiten main loop.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
|
||||||
"github.com/freewilll/apple2/audio"
|
"github.com/freewilll/apple2/audio"
|
||||||
"github.com/freewilll/apple2/cpu"
|
"github.com/freewilll/apple2/cpu"
|
||||||
"github.com/freewilll/apple2/disk"
|
"github.com/freewilll/apple2/disk"
|
||||||
|
@ -13,53 +18,62 @@ import (
|
||||||
"github.com/freewilll/apple2/system"
|
"github.com/freewilll/apple2/system"
|
||||||
"github.com/freewilll/apple2/utils"
|
"github.com/freewilll/apple2/utils"
|
||||||
"github.com/freewilll/apple2/video"
|
"github.com/freewilll/apple2/video"
|
||||||
"github.com/hajimehoshi/ebiten"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var showInstructions *bool
|
var (
|
||||||
var disableFirmwareWait *bool
|
showInstructions *bool // Display all instructions as they are executed
|
||||||
var resetKeysDown bool
|
disableFirmwareWait *bool // Disable the WAIT function at $fca8
|
||||||
var fpsKeysDown bool
|
breakAddress *uint16 // Break address from the command line
|
||||||
var breakAddress *uint16
|
|
||||||
|
resetKeysDown bool // Keep track of ctrl-alt-R key down state
|
||||||
|
fpsKeysDown bool // Keep track of ctrl-alt-F key down state
|
||||||
|
)
|
||||||
|
|
||||||
// checkSpecialKeys checks
|
// checkSpecialKeys checks
|
||||||
// - ctrl-alt-R has been pressed. Releasing the R does a warm reset
|
// - ctrl-alt-R has been pressed. Releasing the R does a warm reset
|
||||||
// - ctrl-alt-F has been pressed, toggling FPS display
|
// - ctrl-alt-F has been pressed, toggling FPS display
|
||||||
func checkSpecialKeys() {
|
func checkSpecialKeys() {
|
||||||
|
// Check for ctrl-alt-R, and if released, do a warm CPU reset
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyR) {
|
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyR) {
|
||||||
resetKeysDown = true
|
resetKeysDown = true
|
||||||
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyR) && resetKeysDown {
|
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyR) && resetKeysDown {
|
||||||
resetKeysDown = false
|
resetKeysDown = false
|
||||||
cpu.Reset()
|
cpu.Reset()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resetKeysDown = false
|
resetKeysDown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for ctrl-alt-F and toggle FPS display
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyF) {
|
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyF) {
|
||||||
fpsKeysDown = true
|
fpsKeysDown = true
|
||||||
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyF) && fpsKeysDown {
|
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyF) && fpsKeysDown {
|
||||||
fpsKeysDown = false
|
fpsKeysDown = false
|
||||||
video.ShowFPS = !video.ShowFPS
|
video.ShowFPS = !video.ShowFPS
|
||||||
fmt.Println("Toggled")
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
fpsKeysDown = false
|
fpsKeysDown = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update is the main ebiten loop
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
keyboard.Poll()
|
|
||||||
checkSpecialKeys()
|
|
||||||
|
|
||||||
system.FrameCycles = 0
|
keyboard.Poll() // Convert ebiten's keyboard state to an interal value
|
||||||
system.LastAudioCycles = 0
|
checkSpecialKeys() // Poll the keyboard and check for R and F keys
|
||||||
exitAtBreak := true
|
|
||||||
|
system.FrameCycles = 0 // Reset cycles processed this frame
|
||||||
|
system.LastAudioCycles = 0 // Reset processed audio cycles
|
||||||
|
exitAtBreak := true // Die if a BRK instruction is seen
|
||||||
|
|
||||||
|
// Run for 1/60 of a second, the duration of an ebiten frame
|
||||||
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, system.CpuFrequency/60)
|
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, system.CpuFrequency/60)
|
||||||
audio.ForwardToFrameCycle()
|
|
||||||
system.Cycles += system.FrameCycles
|
|
||||||
system.FrameCycles = 0
|
|
||||||
|
|
||||||
|
// Process any audio speaker clicks from this frame
|
||||||
|
audio.ForwardToFrameCycle()
|
||||||
|
|
||||||
|
// Updated the cycle accounting
|
||||||
|
system.Cycles += system.FrameCycles
|
||||||
|
|
||||||
|
// Finally render the screen
|
||||||
return video.DrawScreen(screen)
|
return video.DrawScreen(screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,36 +93,35 @@ func main() {
|
||||||
clickWhenDriveHeadMoves := flag.Bool("drive-head-click", false, "Click speaker when drive head moves")
|
clickWhenDriveHeadMoves := flag.Bool("drive-head-click", false, "Click speaker when drive head moves")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
diskImages := flag.Args()
|
|
||||||
var diskImage string
|
|
||||||
if len(diskImages) > 0 {
|
|
||||||
diskImage = diskImages[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
breakAddress = utils.DecodeCmdLineAddress(breakAddressString)
|
breakAddress = utils.DecodeCmdLineAddress(breakAddressString)
|
||||||
|
|
||||||
ebiten.SetRunnableInBackground(true)
|
cpu.InitInstructionDecoder() // Init the instruction decoder data structures
|
||||||
|
mmu.InitRAM() // Set all switches to bootup values and initialize the page tables
|
||||||
|
mmu.InitApple2eROM() // Load the ROM and init page tables
|
||||||
|
mmu.InitIO() // Init slots, video and disk image statuses
|
||||||
|
|
||||||
cpu.InitInstructionDecoder()
|
// If there is a disk image on the command line, load it
|
||||||
mmu.InitRAM()
|
diskImages := flag.Args()
|
||||||
mmu.InitApple2eROM()
|
if len(diskImages) > 0 {
|
||||||
mmu.InitIO()
|
disk.ReadDiskImage(diskImages[0])
|
||||||
|
|
||||||
if diskImage != "" {
|
|
||||||
disk.ReadDiskImage(diskImage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu.Init()
|
cpu.Init() // Init the CPU registers, interrupts and disable testing code
|
||||||
keyboard.Init()
|
keyboard.Init() // Init the keyboard state and ebiten translation tables
|
||||||
video.Init()
|
video.Init() // Init the video data structures used for rendering
|
||||||
audio.InitEbiten()
|
audio.InitEbiten() // Initialize the audio sets up the ebiten output stream
|
||||||
|
|
||||||
audio.Mute = *mute
|
audio.Mute = *mute
|
||||||
audio.ClickWhenDriveHeadMoves = *clickWhenDriveHeadMoves
|
audio.ClickWhenDriveHeadMoves = *clickWhenDriveHeadMoves
|
||||||
system.Init()
|
|
||||||
cpu.SetColdStartReset()
|
|
||||||
cpu.Reset()
|
|
||||||
|
|
||||||
|
system.Init() // Initialize the system-wide state
|
||||||
|
cpu.SetColdStartReset() // Prepare memory to ensure a cold reset
|
||||||
|
cpu.Reset() // Set the CPU and memory states so that a next call to cpu.Run() calls the firmware reset code
|
||||||
|
|
||||||
|
// Start the ebiten main loop
|
||||||
|
ebiten.SetRunnableInBackground(true)
|
||||||
ebiten.Run(update, 280*video.ScreenSizeFactor, 192*video.ScreenSizeFactor, 2, "Apple //e")
|
ebiten.Run(update, 280*video.ScreenSizeFactor, 192*video.ScreenSizeFactor, 2, "Apple //e")
|
||||||
|
|
||||||
|
// The main loop has ended, flush any data to the disk image if any writes have been done.
|
||||||
disk.FlushImage()
|
disk.FlushImage()
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ func (s *stream) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitEbiten initializes the audio sets up the ebiten output stream
|
||||||
func InitEbiten() {
|
func InitEbiten() {
|
||||||
firstAudio = true
|
firstAudio = true
|
||||||
Mute = false
|
Mute = false
|
||||||
|
|
|
@ -28,6 +28,7 @@ var State struct {
|
||||||
P uint8
|
P uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init the CPU registers, interrupts and disable testing code
|
||||||
func Init() {
|
func Init() {
|
||||||
system.RunningTests = false
|
system.RunningTests = false
|
||||||
system.RunningFunctionalTests = false
|
system.RunningFunctionalTests = false
|
||||||
|
@ -858,12 +859,13 @@ func Run(showInstructions bool, breakAddress *uint16, exitAtBreak bool, disableF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetColdStartReset nukes the checkum byte for the reset vector. When this is called, the apple boot firmware will
|
||||||
|
// conclude that the reset vector is invalid and do a cold start.
|
||||||
func SetColdStartReset() {
|
func SetColdStartReset() {
|
||||||
// Nuke the checkum byte for the reset vector. When this is called, the apple boot firmware will
|
|
||||||
// conclude that the reset vector is invalid and do a cold start.
|
|
||||||
mmu.WriteMemory(uint16(0x3f4), uint8(0))
|
mmu.WriteMemory(uint16(0x3f4), uint8(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset sets the CPU and memory states so that a next call to cpu.Run() calls the firmware reset code
|
||||||
func Reset() {
|
func Reset() {
|
||||||
mmu.InitROM()
|
mmu.InitROM()
|
||||||
mmu.InitRAM()
|
mmu.InitRAM()
|
||||||
|
|
|
@ -13,6 +13,7 @@ var strobe uint8
|
||||||
var previousKeysPressed map[uint8]bool
|
var previousKeysPressed map[uint8]bool
|
||||||
var capsLock bool
|
var capsLock bool
|
||||||
|
|
||||||
|
// Init the keyboard state and ebiten translation tables
|
||||||
func Init() {
|
func Init() {
|
||||||
keyBoardData = 0
|
keyBoardData = 0
|
||||||
strobe = 0
|
strobe = 0
|
||||||
|
|
|
@ -29,6 +29,7 @@ var DriveState struct {
|
||||||
Q7 bool
|
Q7 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init initializes the system-wide state
|
||||||
func Init() {
|
func Init() {
|
||||||
Cycles = 0
|
Cycles = 0
|
||||||
AudioChannel = make(chan int16, AudioSampleRate*4) // 1 second
|
AudioChannel = make(chan int16, AudioSampleRate*4) // 1 second
|
||||||
|
|
|
@ -18,23 +18,28 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
charMap *ebiten.Image
|
charMap *ebiten.Image // Character map for text screen
|
||||||
flashCounter int
|
flashCounter int // Counter used for flashing characters on the text screen
|
||||||
flashOn bool
|
flashOn bool // Are we currently flashing?
|
||||||
loresSquares [16]*ebiten.Image
|
loresSquares [16]*ebiten.Image // Colored blocks for lores rendering
|
||||||
ShowFPS bool
|
ShowFPS bool // Show FPS in corner?
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
// initTextCharMap initializes the text character map
|
||||||
var err error
|
func initTextCharMap() {
|
||||||
// The character map pr-latin1.png was downloaded from
|
// The character map pr-latin1.png was downloaded from
|
||||||
// http://www.kreativekorp.com/software/fonts/apple2.shtml
|
// http://www.kreativekorp.com/software/fonts/apple2.shtml
|
||||||
|
|
||||||
|
var err error
|
||||||
charMap, _, err = ebitenutil.NewImageFromFile("video/pr-latin1.png", ebiten.FilterNearest)
|
charMap, _, err = ebitenutil.NewImageFromFile("video/pr-latin1.png", ebiten.FilterNearest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initLoresSquares creates 16 colored squares for the lores renderer
|
||||||
|
func initLoresSquares() {
|
||||||
|
var err error
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
loresSquares[i], err = ebiten.NewImage(7, 4, ebiten.FilterNearest)
|
loresSquares[i], err = ebiten.NewImage(7, 4, ebiten.FilterNearest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,7 +50,6 @@ func Init() {
|
||||||
// From
|
// From
|
||||||
// https://mrob.com/pub/xgithub.com/freewilll/apple2/colors.html
|
// https://mrob.com/pub/xgithub.com/freewilll/apple2/colors.html
|
||||||
// https://archive.org/details/IIgs_2523063_Master_Color_Values
|
// https://archive.org/details/IIgs_2523063_Master_Color_Values
|
||||||
|
|
||||||
alpha := uint8(0xff)
|
alpha := uint8(0xff)
|
||||||
loresSquares[0x00].Fill(color.NRGBA{0, 0, 0, alpha})
|
loresSquares[0x00].Fill(color.NRGBA{0, 0, 0, alpha})
|
||||||
loresSquares[0x01].Fill(color.NRGBA{221, 0, 51, alpha})
|
loresSquares[0x01].Fill(color.NRGBA{221, 0, 51, alpha})
|
||||||
|
@ -63,20 +67,31 @@ func Init() {
|
||||||
loresSquares[0x0D].Fill(color.NRGBA{255, 255, 0, alpha})
|
loresSquares[0x0D].Fill(color.NRGBA{255, 255, 0, alpha})
|
||||||
loresSquares[0x0E].Fill(color.NRGBA{68, 255, 153, alpha})
|
loresSquares[0x0E].Fill(color.NRGBA{68, 255, 153, alpha})
|
||||||
loresSquares[0x0F].Fill(color.NRGBA{255, 255, 255, alpha})
|
loresSquares[0x0F].Fill(color.NRGBA{255, 255, 255, alpha})
|
||||||
|
|
||||||
ShowFPS = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init the video data structures used for rendering
|
||||||
|
func Init() {
|
||||||
|
ShowFPS = false
|
||||||
|
|
||||||
|
initTextCharMap()
|
||||||
|
initLoresSquares()
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawText draws a single text character at x, y. The characters are either normal, inverted or flashing
|
||||||
func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
|
func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||||
|
// Determine if the character is inverted
|
||||||
inverted := false
|
inverted := false
|
||||||
|
|
||||||
if (value & 0xc0) == 0 {
|
if (value & 0xc0) == 0 {
|
||||||
|
// Inverted
|
||||||
inverted = true
|
inverted = true
|
||||||
} else if (value & 0x80) == 0 {
|
} else if (value & 0x80) == 0 {
|
||||||
|
// Flashing
|
||||||
value = value & 0x3f
|
value = value & 0x3f
|
||||||
inverted = flashOn
|
inverted = flashOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the value to a index for the charMap
|
||||||
if !inverted {
|
if !inverted {
|
||||||
value = value & 0x7f
|
value = value & 0x7f
|
||||||
}
|
}
|
||||||
|
@ -89,6 +104,7 @@ func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||||
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
|
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
|
||||||
op.GeoM.Translate(ScreenSizeFactor*7*float64(x), ScreenSizeFactor*8*float64(y))
|
op.GeoM.Translate(ScreenSizeFactor*7*float64(x), ScreenSizeFactor*8*float64(y))
|
||||||
|
|
||||||
|
// Grab the image from the font image
|
||||||
fontRow := value % 16
|
fontRow := value % 16
|
||||||
fontCol := value / 16
|
fontCol := value / 16
|
||||||
var fontX = (int)(15 + fontCol*12)
|
var fontX = (int)(15 + fontCol*12)
|
||||||
|
@ -96,19 +112,24 @@ func drawText(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||||
r := image.Rect(fontX, fontY, fontX+7, fontY+8)
|
r := image.Rect(fontX, fontY, fontX+7, fontY+8)
|
||||||
op.SourceRect = &r
|
op.SourceRect = &r
|
||||||
|
|
||||||
|
// The charMap is already inverted. Invert it back if we ourselves aren't inverted.
|
||||||
if !inverted {
|
if !inverted {
|
||||||
op.ColorM.Scale(-1, -1, -1, 1)
|
op.ColorM.Scale(-1, -1, -1, 1)
|
||||||
op.ColorM.Translate(1, 1, 1, 0)
|
op.ColorM.Translate(1, 1, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make it look greenish
|
||||||
op.ColorM.Scale(0.20, 0.75, 0.20, 1)
|
op.ColorM.Scale(0.20, 0.75, 0.20, 1)
|
||||||
|
|
||||||
return screen.DrawImage(charMap, op)
|
return screen.DrawImage(charMap, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drawLores draws two colored lores squares at the equivalent text location x,y.
|
||||||
func drawLores(screen *ebiten.Image, x int, y int, value uint8) error {
|
func drawLores(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||||
|
// Convert the 8 bit value to two 4 bit values
|
||||||
var values [2]uint8 = [2]uint8{value & 0xf, value >> 4}
|
var values [2]uint8 = [2]uint8{value & 0xf, value >> 4}
|
||||||
|
|
||||||
|
// Render top & bottom squares
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
|
op.GeoM.Scale(ScreenSizeFactor, ScreenSizeFactor)
|
||||||
|
@ -121,10 +142,12 @@ func drawLores(screen *ebiten.Image, x int, y int, value uint8) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drawTextBlock draws a number of lines of text from start to end
|
||||||
func drawTextBlock(screen *ebiten.Image, start int, end int) error {
|
func drawTextBlock(screen *ebiten.Image, start int, end int) error {
|
||||||
for y := start; y < end; y++ {
|
for y := start; y < end; y++ {
|
||||||
base := 128*(y%8) + 40*(y/8)
|
base := 128*(y%8) + 40*(y/8)
|
||||||
|
|
||||||
|
// Flip to the 2nd page if so toggled
|
||||||
if mmu.Page2 {
|
if mmu.Page2 {
|
||||||
base += 0x400
|
base += 0x400
|
||||||
}
|
}
|
||||||
|
@ -132,7 +155,6 @@ func drawTextBlock(screen *ebiten.Image, start int, end int) error {
|
||||||
for x := 0; x < 40; x++ {
|
for x := 0; x < 40; x++ {
|
||||||
offset := textVideoMemory + base + x
|
offset := textVideoMemory + base + x
|
||||||
value := mmu.ReadPageTable[offset>>8][offset&0xff]
|
value := mmu.ReadPageTable[offset>>8][offset&0xff]
|
||||||
|
|
||||||
if err := drawText(screen, x, y, value); err != nil {
|
if err := drawText(screen, x, y, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -142,10 +164,12 @@ func drawTextBlock(screen *ebiten.Image, start int, end int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drawTextBlock draws a number of lores lines from the equivalent text start to end line
|
||||||
func drawLoresBlock(screen *ebiten.Image, start int, end int) error {
|
func drawLoresBlock(screen *ebiten.Image, start int, end int) error {
|
||||||
for y := start; y < end; y++ {
|
for y := start; y < end; y++ {
|
||||||
base := 128*(y%8) + 40*(y/8)
|
base := 128*(y%8) + 40*(y/8)
|
||||||
|
|
||||||
|
// Flip to the 2nd page if so toggled
|
||||||
if mmu.Page2 {
|
if mmu.Page2 {
|
||||||
base += 0x400
|
base += 0x400
|
||||||
}
|
}
|
||||||
|
@ -162,6 +186,7 @@ func drawLoresBlock(screen *ebiten.Image, start int, end int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drawTextOrLoresScreen draws a text and/or lores screen depending on the VideoState
|
||||||
func drawTextOrLoresScreen(screen *ebiten.Image) error {
|
func drawTextOrLoresScreen(screen *ebiten.Image) error {
|
||||||
topHalfIsLowRes := !mmu.VideoState.TextMode
|
topHalfIsLowRes := !mmu.VideoState.TextMode
|
||||||
bottomHalfIsLowRes := !mmu.VideoState.TextMode && !mmu.VideoState.Mixed
|
bottomHalfIsLowRes := !mmu.VideoState.TextMode && !mmu.VideoState.Mixed
|
||||||
|
@ -181,6 +206,7 @@ func drawTextOrLoresScreen(screen *ebiten.Image) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drawHiresScreen draws an entire hires screen. If it's in mixed mode, the lower end is drawn in text.
|
||||||
func drawHiresScreen(screen *ebiten.Image) error {
|
func drawHiresScreen(screen *ebiten.Image) error {
|
||||||
if ScreenSizeFactor != 1 {
|
if ScreenSizeFactor != 1 {
|
||||||
panic("Hires mode for ScreenSizeFactor != 1 not implemented")
|
panic("Hires mode for ScreenSizeFactor != 1 not implemented")
|
||||||
|
@ -188,6 +214,7 @@ func drawHiresScreen(screen *ebiten.Image) error {
|
||||||
|
|
||||||
pixels := make([]byte, 280*192*4)
|
pixels := make([]byte, 280*192*4)
|
||||||
|
|
||||||
|
// Loop over all hires lines
|
||||||
for y := 0; y < 192; y++ {
|
for y := 0; y < 192; y++ {
|
||||||
if mmu.VideoState.Mixed && y >= 160 {
|
if mmu.VideoState.Mixed && y >= 160 {
|
||||||
continue
|
continue
|
||||||
|
@ -196,10 +223,12 @@ func drawHiresScreen(screen *ebiten.Image) error {
|
||||||
// Woz is a genius
|
// Woz is a genius
|
||||||
yOffset := 0x2000 - (0x3d8)*(y>>6) + 0x80*(y>>3) + 0x400*(y&0x7)
|
yOffset := 0x2000 - (0x3d8)*(y>>6) + 0x80*(y>>3) + 0x400*(y&0x7)
|
||||||
|
|
||||||
|
// Flip to the 2nd page if so toggled
|
||||||
if mmu.Page2 {
|
if mmu.Page2 {
|
||||||
yOffset += 0x2000
|
yOffset += 0x2000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For each byte, flip the 7 bits and write it to the pixels array
|
||||||
for x := 0; x < 40; x++ {
|
for x := 0; x < 40; x++ {
|
||||||
offset := yOffset + x
|
offset := yOffset + x
|
||||||
value := mmu.ReadPageTable[offset>>8][offset&0xff]
|
value := mmu.ReadPageTable[offset>>8][offset&0xff]
|
||||||
|
@ -218,14 +247,18 @@ func drawHiresScreen(screen *ebiten.Image) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The hires pixels are read, flush them to the screen
|
||||||
screen.ReplacePixels(pixels)
|
screen.ReplacePixels(pixels)
|
||||||
|
|
||||||
|
// Draw text bit at the bottom
|
||||||
if mmu.VideoState.Mixed {
|
if mmu.VideoState.Mixed {
|
||||||
drawTextBlock(screen, 20, 24)
|
drawTextBlock(screen, 20, 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawScreen draws a text, lores, hires or combination screen
|
||||||
func DrawScreen(screen *ebiten.Image) error {
|
func DrawScreen(screen *ebiten.Image) error {
|
||||||
flashCounter--
|
flashCounter--
|
||||||
if flashCounter < 0 {
|
if flashCounter < 0 {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user