From 37e6f3024fab609a8318d27293b235ffad81ba0a Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Mon, 11 Nov 2019 22:58:42 +0100 Subject: [PATCH] SuperHiRes video mode --- cardVidHD.go | 15 +++++++ screen.go | 9 +++- screenDoubleHiRes.go | 47 +++++++++++++++++++++ screenHiRes.go | 38 ----------------- screenSuperHiRes.go | 97 ++++++++++++++++++++++++++++++++++++++++++++ softSwitches2.go | 6 +++ softSwitches2e.go | 21 ++++++++-- 7 files changed, 190 insertions(+), 43 deletions(-) create mode 100644 screenDoubleHiRes.go create mode 100644 screenSuperHiRes.go diff --git a/cardVidHD.go b/cardVidHD.go index e1f3acc..7658003 100644 --- a/cardVidHD.go +++ b/cardVidHD.go @@ -5,6 +5,7 @@ Simulates just what is needed to make Total Replay use the GS modes if the VidHD See: https://github.com/a2-4am/4cade/blob/master/src/hw.vidhd.a + http://www.applelogic.org/files/GSHARDWAREREF.pdf, page 89 */ type cardVidHD struct { @@ -21,6 +22,20 @@ func buildVidHDRom() []uint8 { return data } +const ( + ioDataNewVideo uint8 = 0x29 +) + func (c *cardVidHD) assign(a *Apple2, slot int) { + // The softswitches are outside the card reverded ss + a.io.addSoftSwitchR(0x22, notImplementedSoftSwitchR, "VIDHD-TBCOLOR") + a.io.addSoftSwitchW(0x22, notImplementedSoftSwitchW, "VIDHD-TBCOLOR") + a.io.addSoftSwitchR(0x29, getStatusSoftSwitch(ioDataNewVideo), "VIDHD-NEWVIDEO") + a.io.addSoftSwitchW(0x29, setStatusSoftSwitch(ioDataNewVideo), "VIDHD-NEWVIDEO") + a.io.addSoftSwitchR(0x34, notImplementedSoftSwitchR, "VIDHD-CLOCKCTL") + a.io.addSoftSwitchW(0x34, notImplementedSoftSwitchW, "VIDHD-CLOCKCTL") + a.io.addSoftSwitchR(0x35, notImplementedSoftSwitchR, "VIDHD-SHADOW") + a.io.addSoftSwitchW(0x35, notImplementedSoftSwitchW, "VIDHD-SHADOW") + c.cardBase.assign(a, slot) } diff --git a/screen.go b/screen.go index f584065..8ce8547 100644 --- a/screen.go +++ b/screen.go @@ -24,6 +24,7 @@ func Snapshot(a *Apple2) *image.RGBA { 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) var lightColor color.Color if isColor { @@ -36,7 +37,9 @@ func Snapshot(a *Apple2) *image.RGBA { } var snap *image.RGBA - if isTextMode { + if isSuperHighResMode { // Has to be first and disables the rest + snap = snapshotSuperHiResMode(a) + } else if isTextMode { snap = snapshotTextMode(a, is80Columns, isSecondPage, false /*isMixMode*/, lightColor) } else { if isHiResMode { @@ -58,7 +61,9 @@ func Snapshot(a *Apple2) *image.RGBA { } } - snap = linesSeparatedFilter(snap) + if !isSuperHighResMode { + snap = linesSeparatedFilter(snap) + } return snap } diff --git a/screenDoubleHiRes.go b/screenDoubleHiRes.go new file mode 100644 index 0000000..9396944 --- /dev/null +++ b/screenDoubleHiRes.go @@ -0,0 +1,47 @@ +package apple2 + +import ( + "image" + "image/color" +) + +const ( + doubleHiResWidth = 2 * hiResWidth +) + +func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light color.Color) *image.RGBA { + // As described in "Inside the Apple IIe" + + height := hiResHeight + if mixedMode { + height = hiResHeightMixed + } + + size := image.Rect(0, 0, doubleHiResWidth, height) + img := image.NewRGBA(size) + for y := 0; y < height; y++ { + lineParts := [][]uint8{ + getHiResLine(a, y, isSecondPage, true), + getHiResLine(a, y, isSecondPage, false), + } + 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) + x++ + for iByte := 0; iByte < hiResLineBytes-1; iByte++ { + for iPart := 0; iPart < 2; iPart++ { + b := lineParts[iPart][iByte] + for j := uint(0); j < 7; j++ { + bit := (b >> j) & 1 + colour := light + if bit == 0 { + colour = color.Black + } + img.Set(x, y, colour) + x++ + } + } + } + } + return img +} diff --git a/screenHiRes.go b/screenHiRes.go index f4c733d..249b802 100644 --- a/screenHiRes.go +++ b/screenHiRes.go @@ -8,7 +8,6 @@ import ( const ( hiResWidth = 280 hiResLineBytes = hiResWidth / 7 - doubleHiResWidth = 2 * hiResWidth hiResHeight = 192 hiResHeightMixed = 160 hiResPage1Address = uint16(0x2000) @@ -72,40 +71,3 @@ func snapshotHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light c } return img } - -func snapshotDoubleHiResModeMono(a *Apple2, isSecondPage bool, mixedMode bool, light color.Color) *image.RGBA { - // As described in "Inside the Apple IIe" - - height := hiResHeight - if mixedMode { - height = hiResHeightMixed - } - - size := image.Rect(0, 0, doubleHiResWidth, height) - img := image.NewRGBA(size) - for y := 0; y < height; y++ { - lineParts := [][]uint8{ - getHiResLine(a, y, isSecondPage, true), - getHiResLine(a, y, isSecondPage, false), - } - 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) - x++ - for iByte := 0; iByte < hiResLineBytes-1; iByte++ { - for iPart := 0; iPart < 2; iPart++ { - b := lineParts[iPart][iByte] - for j := uint(0); j < 7; j++ { - bit := (b >> j) & 1 - colour := light - if bit == 0 { - colour = color.Black - } - img.Set(x, y, colour) - x++ - } - } - } - } - return img -} diff --git a/screenSuperHiRes.go b/screenSuperHiRes.go new file mode 100644 index 0000000..8a4f55f --- /dev/null +++ b/screenSuperHiRes.go @@ -0,0 +1,97 @@ +package apple2 + +import ( + "image" + "image/color" +) + +const ( + shrWidth = 640 + shrWidthBytes = 640 / 4 + shrHeight = 200 + palettesCount = 256 + + shrPixelDataAddress = uint16(0x2000) + shrScanLineControlAddress = uint16(0x9d00) + shrColorPalettesAddress = uint16(0x9e00) + shrColorPalettesAddressEnd = uint16(0xa000) +) + +func snapshotSuperHiResMode(a *Apple2) *image.RGBA { + // See "Apple IIGS Hardware Reference", chapter 4, page 91 + // http://www.applelogic.org/files/GSHARDWAREREF.pdf + + size := image.Rect(0, 0, shrWidth, shrHeight) + img := image.NewRGBA(size) + + // Load the palletes + palleteMem := a.mmu.physicalMainRAMAlt.subRange(shrColorPalettesAddress, shrColorPalettesAddressEnd) + colors := make([]color.Color, palettesCount) + iMem := 0 + for i := 0; i < palettesCount; i++ { + b0 := palleteMem[iMem] + iMem++ + b1 := palleteMem[iMem] + iMem++ + + red := (b0 & 0x0f) << 4 + green := b1 & 0xf0 + blue := (b1 & 0x0f) << 4 + + colors[i] = color.RGBA{red, green, blue, 255} + } + + // See "Apple IIGS Hardware Reference", table 4-21 + palettesSelectionTable := []uint8{0x4, 0x0, 0xc, 0x8} + + // Build the lines + for y := 0; y < shrHeight; y++ { + controlByte := a.mmu.physicalMainRAMAlt.peek(shrScanLineControlAddress + uint16(y)) + is640Wide := (controlByte & 0x80) != 0 + isColorFill := (controlByte & 0x20) != 0 + palleteIndex := (controlByte & 0x0f) << 4 + + lineAddress := shrPixelDataAddress + uint16(shrWidthBytes*y) + lineBytes := a.mmu.physicalMainRAMAlt.subRange(lineAddress, uint16(lineAddress+shrWidthBytes)) + + if is640Wide { + // Line is 640 pixels, two bits per pixel + x := 0 + for i := 0; i < shrWidthBytes; i++ { + b := lineBytes[i] + for j := 3; j >= 0; j-- { + p := (b >> (uint(j) * 2)) & 0x03 + offset := palettesSelectionTable[j] + color := colors[palleteIndex+offset+p] + img.Set(x, y, color) + x++ + } + } + } else { + // Line is 320 pixels, two pixels per byte + x := 0 + previousColor := uint8(0) + for i := 0; i < shrWidthBytes; i++ { + p0 := (lineBytes[i] & 0xf0) >> 4 + if isColorFill && p0 == 0 { + p0 = previousColor + } else { + previousColor = p0 + } + p1 := lineBytes[i] & 0x0f + if isColorFill && p1 == 0 { + p1 = previousColor + } else { + previousColor = p1 + } + img.Set(x, y, colors[palleteIndex+p0]) + img.Set(x+1, y, colors[palleteIndex+p0]) + img.Set(x+2, y, colors[palleteIndex+p1]) + img.Set(x+3, y, colors[palleteIndex+p1]) + x += 4 + } + } + } + + return img +} diff --git a/softSwitches2.go b/softSwitches2.go index 67725d7..c25e582 100644 --- a/softSwitches2.go +++ b/softSwitches2.go @@ -80,6 +80,12 @@ func notImplementedSoftSwitchR(*ioC0Page) uint8 { func notImplementedSoftSwitchW(*ioC0Page, uint8) { } +func setStatusSoftSwitch(ioFlag uint8) softSwitchW { + return func(io *ioC0Page, value uint8) { + io.softSwitchesData[ioFlag] = value + } +} + func getStatusSoftSwitch(ioFlag uint8) softSwitchR { return func(io *ioC0Page) uint8 { return io.softSwitchesData[ioFlag] diff --git a/softSwitches2e.go b/softSwitches2e.go index 5fef5b0..433ac10 100644 --- a/softSwitches2e.go +++ b/softSwitches2e.go @@ -8,7 +8,11 @@ package apple2 const ( ioFlagAltChar uint8 = 0x1E ioFlag80Col uint8 = 0x1F - // ??? ioVertBlank uin8 = 0x19 +) + +const ( + screenDrawCycles = uint64(12480 + 4550) + screenVertBlankingCycles = uint64(4550) ) func addApple2ESoftSwitches(io *ioC0Page) { @@ -38,8 +42,19 @@ func addApple2ESoftSwitches(io *ioC0Page) { return ssFromBool(mmu.lcActiveRead) }, "BSRREADRAM") - // TOOD: - // VBL or VERTBLANK read on 0x19 + io.addSoftSwitchR(0x19, func(_ *ioC0Page) uint8 { + // See "Inside Apple IIe", page 268 + // See http://rich12345.tripod.com/aiivideo/vbl.html + // For each screen draw: + // 12480 cycles drawing lines, VERTBLANK = $00 + // 4550 cycles doing the return to position (0,0), VERTBLANK = $80 + // Vert blank takes 12480 cycles every page redraw + cycles := io.apple2.cpu.GetCycles() % screenDrawCycles + if cycles <= screenVertBlankingCycles { + return ssOn + } + return ssOff + }, "VERTBLANK") //io.softSwitchesData[ioFlagAltChar] = ssOn // Not sure about this.