diff options
Diffstat (limited to 'app/mac')
| -rw-r--r-- | app/mac/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | app/mac/main.mm | 60 | ||||
| -rw-r--r-- | app/mac/scene.hh | 12 | ||||
| -rw-r--r-- | app/mac/scene.mm | 179 |
4 files changed, 271 insertions, 0 deletions
diff --git a/app/mac/CMakeLists.txt b/app/mac/CMakeLists.txt new file mode 100644 index 0000000..a4f25c9 --- /dev/null +++ b/app/mac/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(nes_mac) + +find_library(FRAMEWORK_COCOA Cocoa REQUIRED) +find_library(FRAMEWORK_SPRITEKIT SpriteKit REQUIRED) + +target_include_directories(nes_mac PRIVATE .) +target_link_libraries( + nes_mac + PRIVATE + nes::options + nes::nes + ${FRAMEWORK_COCOA} + ${FRAMEWORK_SPRITEKIT}) + +target_sources( + nes_mac + PRIVATE + main.mm + scene.hh + scene.mm)
\ No newline at end of file diff --git a/app/mac/main.mm b/app/mac/main.mm new file mode 100644 index 0000000..5ca7c5c --- /dev/null +++ b/app/mac/main.mm @@ -0,0 +1,60 @@ +#import "scene.hh" +#import <iostream> + +#import <Cocoa/Cocoa.h> +#import <SpriteKit/SpriteKit.h> + +int main(int argc, char** argv) +{ + if (argc != 2) + { + std::cerr << "Usage: " << argv[0] << " <rom-path>" << std::endl; + return 1; + } + + auto filePath = [NSString stringWithUTF8String:argv[1]]; + + auto app = [NSApplication sharedApplication]; + [app setActivationPolicy:NSApplicationActivationPolicyRegular]; + + auto quitItem = [[NSMenuItem alloc] init]; + [quitItem setTitle:@"Quit NES"]; + [quitItem setKeyEquivalent:@"q"]; + [quitItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + [quitItem setTarget:app]; + [quitItem setAction:@selector(terminate:)]; + + auto menu = [[NSMenu alloc] init]; + [menu addItem:quitItem]; + + auto item = [[NSMenuItem alloc] init]; + [item setSubmenu:menu]; + + auto mainMenu = [[NSMenu alloc] init]; + [mainMenu addItem:item]; + [app setMainMenu:mainMenu]; + + auto scene = [[Scene alloc] initWithFilePath:filePath]; + auto minSize = [scene size]; + + auto view = [[SKView alloc] init]; + [view setIgnoresSiblingOrder:YES]; + [view setShowsFPS:YES]; + [view presentScene:scene]; + + auto window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0., 0., minSize.width * 2, minSize.height * 2) + styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowMiniaturizeButton | NSWindowStyleMaskResizable) + backing:NSBackingStoreBuffered + defer:false]; + [window setTitle:@"NES"]; + [window setContentAspectRatio:minSize]; + [window setContentMinSize:minSize]; + [window setContentView:view]; + [window makeKeyAndOrderFront:nil]; + + [app activateIgnoringOtherApps:YES]; + [app run]; + + return 0; +}
\ No newline at end of file diff --git a/app/mac/scene.hh b/app/mac/scene.hh new file mode 100644 index 0000000..1660e66 --- /dev/null +++ b/app/mac/scene.hh @@ -0,0 +1,12 @@ +#pragma once + +#import <SpriteKit/SpriteKit.h> + +@interface Scene : SKScene + +- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER; + ++ (instancetype)sceneWithSize:(CGSize)size NS_UNAVAILABLE; +- (instancetype)initWithSize:(CGSize)size NS_UNAVAILABLE; + +@end
\ No newline at end of file diff --git a/app/mac/scene.mm b/app/mac/scene.mm new file mode 100644 index 0000000..f858185 --- /dev/null +++ b/app/mac/scene.mm @@ -0,0 +1,179 @@ +#import "scene.hh" +#import "nes/nes.hh" +#import "nes/display.hh" +#import "nes/util/rgb.hh" +#import <memory> +#import <optional> + +namespace +{ + class display_impl : public nes::display + { + SKSpriteNode* node_; + NSMutableData* buffer_front_; + NSMutableData* buffer_back_; + std::uint8_t* bytes_back_; + + public: + explicit display_impl(SKSpriteNode* node) + : node_{ node } + , buffer_front_{ make_buffer() } + , buffer_back_{ make_buffer() } + , bytes_back_{ static_cast<std::uint8_t*>([buffer_back_ mutableBytes]) } + { + } + + ~display_impl() = default; + + virtual auto switch_buffers() -> void override + { + auto tmp = buffer_back_; + buffer_back_ = buffer_front_; + buffer_front_ = tmp; + bytes_back_ = static_cast<std::uint8_t*>([buffer_back_ mutableBytes]); + } + + auto actually_switch_buffers() -> void + { + auto texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height) flipped:YES]; + [texture setFilteringMode:SKTextureFilteringNearest]; + [node_ setTexture:texture]; + } + + virtual auto set(unsigned x, unsigned y, nes::rgb value) -> void override + { + auto const offset = (y * width + x) * 4; + bytes_back_[offset + 0] = value.r; + bytes_back_[offset + 1] = value.g; + bytes_back_[offset + 2] = value.b; + bytes_back_[offset + 3] = 255; + } + + virtual auto get(unsigned x, unsigned y) const -> nes::rgb override + { + auto const offset = (y * width + x) * 4; + auto res = nes::rgb{}; + res.r = bytes_back_[offset + 0]; + res.g = bytes_back_[offset + 1]; + res.b = bytes_back_[offset + 2]; + return res; + } + + private: + static auto make_buffer() -> NSMutableData* + { + auto const length = NSUInteger{ width * height * 4 }; + return [NSMutableData dataWithLength:length]; + } + }; +} // namespace + +@implementation Scene +{ + std::unique_ptr<display_impl> display_; + std::unique_ptr<nes::nes> nes_; + std::optional<NSTimeInterval> last_time_; +} + +- (instancetype)initWithFilePath:(NSString*)filePath +{ + self = [super initWithSize:CGSizeMake(nes::display::width, nes::display::height)]; + if (self) + { + auto node = [[SKSpriteNode alloc] init]; + [node setAnchorPoint:CGPointZero]; + [node setSize:[self size]]; + + [self setScaleMode:SKSceneScaleModeAspectFit]; + [self addChild:node]; + + auto cartridge = nes::cartridge::from_file([filePath UTF8String]); + + self->display_ = std::make_unique<display_impl>(node); + self->nes_ = std::make_unique<nes::nes>(std::move(cartridge), *self->display_); + } + return self; +} + +- (void)keyDown:(NSEvent*)event +{ + auto& controller_1 = self->nes_->get_controller_1(); + + switch ([event keyCode]) + { + case 0x7e: // up arrow + controller_1.get_pressed().add(nes::buttons::up); + break; + case 0x7d: // down arrow + controller_1.get_pressed().add(nes::buttons::down); + break; + case 0x7b: // left arrow + controller_1.get_pressed().add(nes::buttons::left); + break; + case 0x7c: // right arrow + controller_1.get_pressed().add(nes::buttons::right); + break; + case 0x6: // "Z" + controller_1.get_pressed().add(nes::buttons::a); + break; + case 0x7: // "X" + controller_1.get_pressed().add(nes::buttons::b); + break; + case 0x24: // return + controller_1.get_pressed().add(nes::buttons::start); + break; + case 0x31: // space + controller_1.get_pressed().add(nes::buttons::select); + break; + default: + [super keyDown:event]; + break; + } +} + +- (void)keyUp:(NSEvent*)event +{ + auto& controller_1 = self->nes_->get_controller_1(); + + switch ([event keyCode]) + { + case 0x7e: // up arrow + controller_1.get_pressed().remove(nes::buttons::up); + break; + case 0x7d: // down arrow + controller_1.get_pressed().remove(nes::buttons::down); + break; + case 0x7b: // left arrow + controller_1.get_pressed().remove(nes::buttons::left); + break; + case 0x7c: // right arrow + controller_1.get_pressed().remove(nes::buttons::right); + break; + case 0x6: // "Z" + controller_1.get_pressed().remove(nes::buttons::a); + break; + case 0x7: // "X" + controller_1.get_pressed().remove(nes::buttons::b); + break; + case 0x24: // return + controller_1.get_pressed().remove(nes::buttons::start); + break; + case 0x31: // space + controller_1.get_pressed().remove(nes::buttons::select); + break; + default: + [super keyUp:event]; + break; + } +} + +- (void)update:(NSTimeInterval)currentTime +{ + auto const last_time = self->last_time_.value_or(currentTime); + last_time_ = currentTime; + auto const delta_us = static_cast<std::uint64_t>((currentTime - last_time) * 1000. * 1000.); + self->nes_->step(std::chrono::microseconds{ delta_us }); + self->display_->actually_switch_buffers(); +} + +@end
\ No newline at end of file |
