// // 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 . // //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.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..= 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) } }