mirror of
https://github.com/trudnai/Steve2.git
synced 2024-12-22 06:29:15 +00:00
486 lines
17 KiB
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)
|
|
}
|
|
|
|
|
|
|
|
}
|