Steve2/A2Mac/HiRes.swift

486 lines
17 KiB
Swift

//
// HiRes.swift
// Steve ][
//
// Created by Tamas Rudnai on 9/19/19.
// Copyright © 2019, 2020 Tamas Rudnai. All rights reserved.
//
// This file is part of Steve ][ -- The Apple ][ Emulator.
//
// Steve ][ is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Steve ][ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Steve ][. If not, see <https://www.gnu.org/licenses/>.
//
//import Foundation
import AppKit
class HiRes: NSView {
static let PageSize = 0x2000
static let Page1Addr = 0x2000
static let Page2Addr = 0x4000
static let PixelWidth = 280
static let PixelMixedHeight = 160
static let PixelHeight = 192
static let MixedTextHeight = 4
static let MixedHeight = 160
static let blockRows = 24
static let blockCols = 40
static let blockWidth = PixelWidth / blockCols
static let blockHeight = PixelHeight / blockRows
let HiResBuffer1 = UnsafeRawBufferPointer(start: MEM + Page1Addr, count: PageSize * 2)
let HiResBuffer2 = UnsafeRawBufferPointer(start: MEM + Page2Addr, count: PageSize * 2)
var HiResBufferPointer = UnsafeRawBufferPointer(start: MEM + Page1Addr, count: PageSize * 2)
let HiResRawPointer = UnsafeRawPointer(RAM + Page1Addr)
// holds the starting addresses for each lines minus the screen page starting address
var HiResLineAddrTbl = [Int](repeating: 0, count: PixelHeight)
func initHiResLineAddresses() {
var i = 0
for x in stride(from: 0, through: 0x50, by: 0x28) {
for y in stride(from: 0, through: 0x380, by: 0x80) {
for z in stride(from: 0, through: 0x1C00, by: 0x400) {
HiResLineAddrTbl[i] = x + y + z
i += 1
}
}
}
}
var HiResSubView = [[NSView]]()
func createHiRes() {
for y in 0 ..< HiRes.blockRows {
HiResSubView.append([NSView]())
for x in 0 ..< HiRes.blockCols {
let blockView = NSView(frame: NSRect(x: x * HiRes.blockWidth, y: y * 8, width: HiRes.blockWidth, height: 8))
HiResSubView[y].append(blockView)
self.addSubview(blockView)
}
}
}
func clearScreen() {
HiRes.context?.clear( CGRect(x: 0, y: 0, width: frame.width, height: frame.height) )
needsDisplay = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initHiResLineAddresses()
clearScreen()
// currentContext?.setShouldAntialias(false)
// currentContext?.interpolationQuality = CGInterpolationQuality.none
// let scaleSizeW = Double((frame.size).width) / Double(HiRes.PixelWidth)
// let scaleSizeH = Double((frame.size).height) / Double(HiRes.PixelHeight)
// let scaleSizeW = 4
// let scaleSizeH = 4
// scaleUnitSquare(to: NSSize(width: scaleSizeW, height: scaleSizeH))
// create smaller box views for draw optimization
createHiRes()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
static func createBitmapContext(pixelsWide: Int, _ pixelsHigh: Int) -> CGContext? {
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * pixelsWide
let byteCount = (bytesPerRow * pixelsHigh)
// guard let colorSpace = CGColorSpace(name: CGColorSpace.linearSRGB) else { return nil }
// guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else { return nil }
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else { return nil }
let pixels = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: byteCount)
let bitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
let context = CGContext(
data: pixels,
width: pixelsWide,
height: pixelsHigh,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: bitmapInfo)
return context
}
private var currentContext : CGContext? {
get {
if #available(OSX 10.10, *) {
return NSGraphicsContext.current?.cgContext
} else if let contextPointer = NSGraphicsContext.current?.graphicsPort {
let context: CGContext = Unmanaged.fromOpaque(contextPointer).takeUnretainedValue()
return context
}
return nil
}
}
static let ScreenBitmapSize = (PixelWidth * PixelHeight * 4)
static let context = createBitmapContext(pixelsWide: PixelWidth, PixelHeight)
static let pixels = UnsafeMutableRawBufferPointer(start: context?.data, count: ScreenBitmapSize)
static var pixelsSRGB = pixels.bindMemory(to: UInt32.self)
let R = 2
let G = 1
let B = 0
let A = 3
var blockChanged = [Bool](repeating: false, count: HiRes.blockRows * HiRes.blockCols)
var shadowScreen = [Int](repeating: 0, count: PageSize)
var was = 0;
// let color_black : UInt32 = 0x00000000;
// let color_white : UInt32 = 0xEEEEEEEE;
// let color_purple : UInt32 = 0xFFBB11EE;
// let color_green : UInt32 = 0xFF0BBB11;
// let color_blue : UInt32 = 0xFF1155FF;
// let color_orange : UInt32 = 0xFFEE2211;
// HiRes Colors for the SRGB color space
let color_black : UInt32 = 0x00000000;
let color_white : UInt32 = 0xFFEEEEEE;
let color_purple : UInt32 = 0xFFDD55FF;
let color_green : UInt32 = 0xFF2BD84A;
let color_blue : UInt32 = 0xFF5599FF;
let color_orange : UInt32 = 0xFFFF6302;
// for debugging only:
let color_turquis : UInt32 = 0xFF11BBBB;
let color_yellow : UInt32 = 0xFFBBBB11;
// default is green
var monoColor : UInt32 = 0xFF2BD84A;
func refreshChanged( blockSize : Int ) {
// refresh changed block only
let screenBlockMargin = 12 / blockSize
let blockScreenWidth = Int(frame.width) / HiRes.blockCols * blockSize
let blockScreenHeigth = Int(frame.height) / HiRes.blockRows
for blockVertIdx in 0 ..< HiRes.blockRows {
for blockHorIdx in 0 ..< HiRes.blockCols / blockSize {
if blockChanged[ blockVertIdx * HiRes.blockCols / blockSize + blockHorIdx ] {
// refresh the entire screen
let boundingBox = CGRect(
x: blockHorIdx * blockScreenWidth - screenBlockMargin,
y: Int(frame.height) - blockVertIdx * blockScreenHeigth - blockScreenHeigth - screenBlockMargin,
width: blockScreenWidth + screenBlockMargin * blockSize,
height: blockScreenHeigth + screenBlockMargin * blockSize)
self.setNeedsDisplay( boundingBox )
}
}
}
}
func RenderMono() {
var height = HiRes.PixelHeight
// do not even render it...
if videoMode.text == 1 {
return
}
else {
if videoMode.mixed == 1 {
height = HiRes.MixedHeight
}
if MEMcfg.txt_page_2 == 1 {
HiResBufferPointer = HiResBuffer2
}
else {
HiResBufferPointer = HiResBuffer1
}
}
var pixelAddr = 0
var y = 0
blockChanged = [Bool](repeating: false, count: HiRes.blockRows * HiRes.blockCols)
for lineAddr in HiResLineAddrTbl {
if ( height <= 0 ) {
break
}
height -= 1
let blockVertIdx = y / HiRes.blockHeight * HiRes.blockCols
for blockHorIdx in 0..<HiRes.blockCols {
let block = Int(HiResBufferPointer[ Int(lineAddr + blockHorIdx) ])
let screenIdx = y * HiRes.blockCols + blockHorIdx
// get all changed blocks
blockChanged[ blockVertIdx + blockHorIdx ] = blockChanged[ blockVertIdx + blockHorIdx ] || shadowScreen[ screenIdx ] != block
shadowScreen[ screenIdx ] = block
for bit in stride(from: 0, through: 6, by: 1) {
let bitMask = 1 << bit
if (block & bitMask) != 0 {
HiRes.pixelsSRGB[pixelAddr] = monoColor;
}
else {
HiRes.pixelsSRGB[pixelAddr] = color_black;
}
pixelAddr += 1
}
}
y += 1
}
refreshChanged(blockSize: 1)
}
func colorPixel ( pixelAddr : Int, pixel : Int, prev : Int ) {
let colorAddr = pixelAddr / 4
switch ( pixel ) {
case 1: // purple (bits are in reverse!)
HiRes.pixelsSRGB[colorAddr] = color_purple;
// HiRes.pixelsSRGB[colorAddr + 1] = color_purple
if (colorAddr >= 1) && (prev != 0x03) && (prev != 0x07) && (prev != 0x00) && (prev != 0x04) {
HiRes.pixelsSRGB[colorAddr - 1] = color_purple
}
case 2: // green
// reducing color bleeding
if (colorAddr > 0) && (HiRes.pixelsSRGB[colorAddr - 1] != color_black) {
HiRes.pixelsSRGB[colorAddr] = color_green
}
HiRes.pixelsSRGB[colorAddr + 1] = color_green
case 3: // white 1
// if ( colorAddr >= 2 ) && ( HiRes.pixelsSRGB[colorAddr - 2] != color_black ) {
// HiRes.pixelsSRGB[colorAddr - 1] = HiRes.pixelsSRGB[colorAddr - 2]
// }
// if (colorAddr >= 1) {
// HiRes.pixelsSRGB[colorAddr - 1] = color_yellow
// }
HiRes.pixelsSRGB[colorAddr] = color_white
HiRes.pixelsSRGB[colorAddr + 1] = color_white
case 5: // blue
HiRes.pixelsSRGB[colorAddr] = color_blue
if (colorAddr >= 1) && (prev != 0x00) && (prev != 0x04) {
HiRes.pixelsSRGB[colorAddr - 1] = color_blue
}
case 6: // orange
// reducing color bleeding
if (colorAddr > 0) && (HiRes.pixelsSRGB[colorAddr - 1] != color_black) {
HiRes.pixelsSRGB[colorAddr] = color_orange
}
HiRes.pixelsSRGB[colorAddr + 1] = color_orange
case 7: // white 2
HiRes.pixelsSRGB[colorAddr] = color_white
HiRes.pixelsSRGB[colorAddr + 1] = color_white
default: // 0x00 (black 1), 0x04 (black 2)
HiRes.pixelsSRGB[colorAddr] = color_black
HiRes.pixelsSRGB[colorAddr + 1] = color_black
break
}
// white adjustment
if ( (prev & 2) == 2 ) && ( (pixel & 1) == 1 ) {
HiRes.pixelsSRGB[colorAddr] = color_white
if (colorAddr >= 1) {
HiRes.pixelsSRGB[colorAddr - 1] = color_white
}
// TODO: Need better check if extra green was created
if (colorAddr >= 2) && (HiRes.pixelsSRGB[colorAddr - 2] == color_green ) {
if (colorAddr < 3) || (HiRes.pixelsSRGB[colorAddr - 3] != color_green) {
HiRes.pixelsSRGB[colorAddr - 2] = color_black
}
}
}
// // green adjustment -- followed by white
// if (colorAddr >= 1) && (prev == 0x03) && (HiRes.pixelsSRGB[colorAddr - 1] = color_green) {
// HiRes.pixelsSRGB[colorAddr - 1] = color_green
// }
// purple adjustment -- followed by white
if (prev == 0x01) && (
(pixel == 0x01) ||
(pixel == 0x03) || (pixel == 0x07) // white
) {
// was the previous purple pixel promoted to white or is it still purple?
if (colorAddr >= 2) && ( HiRes.pixelsSRGB[colorAddr - 2] == color_purple ) {
HiRes.pixelsSRGB[colorAddr - 1] = color_purple
}
}
// blue adjustment -- followed by white
else if (prev == 0x05) && (
(pixel == 0x05) ||
(pixel == 0x03) || (pixel == 0x07) // white
) {
HiRes.pixelsSRGB[colorAddr - 1] = color_blue
}
}
func RenderColor() {
var height = HiRes.PixelHeight
// do not even render it...
if videoMode.text == 1 {
return
}
else {
if videoMode.mixed == 1 {
height = HiRes.MixedHeight
}
if MEMcfg.txt_page_2 == 1 {
HiResBufferPointer = HiResBuffer2
}
else {
HiResBufferPointer = HiResBuffer1
}
}
var pixelAddr = 0
var y = 0
blockChanged = [Bool](repeating: false, count: HiRes.blockRows * HiRes.blockCols / 2)
HiRes.context?.clear( CGRect(x: 0, y: 0, width: frame.width, height: frame.height) )
for lineAddr in HiResLineAddrTbl {
if ( height <= 0 ) {
break
}
height -= 1
let blockVertIdx = y / HiRes.blockHeight * HiRes.blockCols / 2
var prev = 0
for blockHorIdx in 0 ..< HiRes.blockCols / 2 {
// print("blockVertIdx:", blockVertIdx, " blockHorIdx:", blockHorIdx)
let blockH = Int(HiResBufferPointer[ Int(lineAddr + blockHorIdx * 2) ])
let blockH7 = ( blockH >> 5 ) & 0x04
let blockL = Int(HiResBufferPointer[ Int(lineAddr + blockHorIdx * 2) + 1 ])
let blockL7 = ( blockL >> 5 ) & 0x04
let block = ( ( blockL & 0x7F ) << 7 ) | ( blockH & 0x7F )
let block14 = ( blockL << 8 ) | blockH
let screenIdx = y * HiRes.blockCols + blockHorIdx
// get all changed blocks
blockChanged[ blockVertIdx + blockHorIdx ] = blockChanged[ blockVertIdx + blockHorIdx ] || shadowScreen[ screenIdx ] != block14
shadowScreen[ screenIdx ] = block14
for px in 0 ... 2 {
// let bitMask = 3 << ( px * 2 )
let pixel = blockH7 | ( (block >> (px * 2)) & 3 )
colorPixel(pixelAddr: pixelAddr, pixel: pixel, prev: prev )
pixelAddr += 8
prev = pixel
}
let pixel = (blockL7 | blockH7) | ( (block >> (3 * 2)) & 3 )
colorPixel(pixelAddr: pixelAddr, pixel: pixel, prev: prev )
pixelAddr += 8
prev = pixel
for px in 4 ... 6 {
// let bitMask = 3 << ( px * 2 )
let pixel = blockL7 | ( (block >> (px * 2)) & 3 )
colorPixel(pixelAddr: pixelAddr, pixel: pixel, prev: prev )
pixelAddr += 8
prev = pixel
}
}
y += 1
}
// refresh changed block only
refreshChanged(blockSize: 2)
// needsDisplay = true // refresh the entire screen
}
func Render() {
if ( ViewController.current?.ColorMonitor ?? true ) {
RenderColor()
}
else {
RenderMono()
}
}
func RenderFullScreen() {
needsDisplay = true
Render()
}
override func draw(_ rect: CGRect) {
guard let image = HiRes.context?.makeImage() else { return }
// refresh the entire screen
let boundingBox = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
if ( ViewController.current?.CRTMonitor ?? false ) {
currentContext?.interpolationQuality = .high // TODO: Make a switch that lets you turn on and off "old monitor effects"
}
else {
currentContext?.interpolationQuality = .none
}
currentContext?.draw(image, in: boundingBox)
}
}