mirror of
https://github.com/Luigi30/FruitMachine-Swift.git
synced 2024-06-09 23:29:27 +00:00
163 lines
6.5 KiB
Swift
163 lines
6.5 KiB
Swift
//
|
|
// ScreenDelegate.swift
|
|
// FruitMachine
|
|
//
|
|
// Created by Christopher Rohl on 8/1/17.
|
|
// Copyright © 2017 Christopher Rohl. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
|
|
extension AppleII {
|
|
|
|
enum CharacterAttributes {
|
|
case normal
|
|
case flashing
|
|
case inverse
|
|
}
|
|
|
|
class ScreenDelegate: NSObject, CALayerDelegate {
|
|
static let PIXEL_WIDTH = 280
|
|
static let PIXEL_HEIGHT = 192
|
|
|
|
static let CELLS_WIDTH = 40
|
|
static let CELLS_HEIGHT = 24
|
|
static let CELLS_COUNT = CELLS_WIDTH * CELLS_HEIGHT
|
|
|
|
var flashIsInverse = false
|
|
|
|
/* Pixel data stuff. */
|
|
let bitmapInfo: CGBitmapInfo = [.byteOrder16Big, CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)]
|
|
|
|
var pixels: CVPixelBuffer?
|
|
|
|
let sourceRowBytes: Int
|
|
let bufferWidth: Int
|
|
let bufferHeight: Int
|
|
|
|
var renderedImage: CGImage?
|
|
|
|
var scanlineOffsets: [Int]
|
|
|
|
override init() {
|
|
_ = CVPixelBufferCreate(kCFAllocatorDefault, AppleII.ScreenDelegate.PIXEL_WIDTH, AppleII.ScreenDelegate.PIXEL_HEIGHT, OSType(k16BE555PixelFormat), nil, &pixels)
|
|
|
|
sourceRowBytes = CVPixelBufferGetBytesPerRow(pixels!)
|
|
bufferWidth = CVPixelBufferGetWidth(pixels!)
|
|
bufferHeight = CVPixelBufferGetHeight(pixels!)
|
|
|
|
renderedImage = nil
|
|
|
|
scanlineOffsets = [Int]()
|
|
for i in 0..<AppleII.ScreenDelegate.PIXEL_HEIGHT {
|
|
scanlineOffsets.append(i * AppleII.ScreenDelegate.PIXEL_WIDTH)
|
|
}
|
|
}
|
|
|
|
func putGlyph(buffer: UnsafeMutablePointer<BitmapPixelsBE555.PixelData>?, glyph: Glyph, attributes: UInt8, pixelPosition: CGPoint) {
|
|
//You better have locked the buffer before getting here...
|
|
if(pixelPosition.x == -1 && pixelPosition.y == -1) { return }
|
|
|
|
let ca: CharacterAttributes
|
|
if(attributes == 0x00) {
|
|
ca = .inverse
|
|
} else if(attributes == 0x40) {
|
|
ca = .flashing
|
|
} else {
|
|
ca = .normal
|
|
}
|
|
|
|
//Calculate the offset to reach the desired position.
|
|
let baseOffset = scanlineOffsets[Int(pixelPosition.y)] + Int(pixelPosition.x)
|
|
|
|
for charY in 0..<AppleII.A2CharacterGenerator.CHAR_HEIGHT {
|
|
let offset = baseOffset + (AppleII.ScreenDelegate.PIXEL_WIDTH * charY)
|
|
let glyphOffsetY = (charY * 8)
|
|
|
|
for charX in 0..<7 {
|
|
switch(ca) {
|
|
case .normal:
|
|
buffer![offset + 6 - charX] = glyph.pixels[glyphOffsetY + charX]
|
|
case .inverse:
|
|
buffer![offset + 6 - charX] = BitmapPixelsBE555.PixelData(data: ~glyph.pixels[glyphOffsetY + charX].data)
|
|
case .flashing:
|
|
if(!flashIsInverse) {
|
|
buffer![offset + 6 - charX] = glyph.pixels[glyphOffsetY + charX]
|
|
} else {
|
|
buffer![offset + 6 - charX] = BitmapPixelsBE555.PixelData(data: ~glyph.pixels[glyphOffsetY + charX].data)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func getPixelOffset(charCellX: Int, charCellY: Int) -> CGPoint {
|
|
return CGPoint(x: charCellX * 7, y: charCellY * 8)
|
|
}
|
|
|
|
func getPixelOffset(memoryOffset: Int) -> CGPoint {
|
|
//Offset is between 0x000 and 0x3FF.
|
|
//If offset & 0x28, second batch.
|
|
//If offset & 0x50, third batch.
|
|
//Else, first batch.
|
|
|
|
var rowNumber = memoryOffset / 0x80
|
|
let lowByte = memoryOffset & 0x0FF
|
|
let cellX: Int
|
|
|
|
if(0x28 ... 0x4F ~= lowByte || 0xA8 ... 0xCF ~= lowByte) {
|
|
//Middle third.
|
|
rowNumber += 8
|
|
cellX = (lowByte & ~(0x80)) - 0x28
|
|
}
|
|
else if(0x50 ... 0x77 ~= lowByte || 0xD0 ... 0xF7 ~= lowByte) {
|
|
//Bottom third.
|
|
rowNumber += 16
|
|
cellX = (lowByte & ~(0x80)) - 0x50
|
|
}
|
|
else if(0x78 ... 0x7F ~= lowByte || 0xF8 ... 0xFF ~= lowByte) {
|
|
//Discard.
|
|
return CGPoint(x: -1, y: -1)
|
|
}
|
|
else {
|
|
//Top third.
|
|
rowNumber += 0
|
|
cellX = (lowByte & ~(0x80))
|
|
}
|
|
|
|
return getPixelOffset(charCellX: cellX, charCellY: rowNumber)
|
|
}
|
|
|
|
/* Draw the screen. */
|
|
func draw(_ layer: CALayer, in ctx: CGContext) {
|
|
CVPixelBufferLockBaseAddress(pixels!, CVPixelBufferLockFlags.readOnly)
|
|
let pixelBase = CVPixelBufferGetBaseAddress(pixels!)
|
|
let pixelRef = CGDataProvider(dataInfo: nil, data: pixelBase!, size: sourceRowBytes * bufferHeight, releaseData: releaseMaskImagePixelData)
|
|
|
|
renderedImage = CGImage(width: AppleII.ScreenDelegate.PIXEL_WIDTH,
|
|
height: AppleII.ScreenDelegate.PIXEL_HEIGHT,
|
|
bitsPerComponent: Int(BitmapPixelsBE555.bitsPerComponent), //5
|
|
bitsPerPixel: Int(BitmapPixelsBE555.bitsPerPixel), //16
|
|
bytesPerRow: AppleII.ScreenDelegate.PIXEL_WIDTH * Int(MemoryLayout<BitmapPixelsBE555.PixelData>.size),
|
|
space: BitmapPixelsBE555.colorSpace, //RGB
|
|
bitmapInfo: bitmapInfo, //BE555
|
|
provider: pixelRef!,
|
|
decode: nil,
|
|
shouldInterpolate: false,
|
|
intent: CGColorRenderingIntent.defaultIntent)
|
|
|
|
ctx.draw(renderedImage!, in: layer.bounds)
|
|
|
|
CVPixelBufferUnlockBaseAddress(pixels!, CVPixelBufferLockFlags(rawValue: 0))
|
|
}
|
|
|
|
let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
|
|
// https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
|
|
// N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
|
|
return
|
|
}
|
|
}
|
|
|
|
}
|