summaryrefslogtreecommitdiff
path: root/app/mac
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-01-23 22:02:11 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-01-23 22:02:11 +0100
commitb1d1573702f21e3104606d6ade5f5d39ec86a33a (patch)
treed703be130a5d6f9ff2626313d3380ed34de44dd7 /app/mac
parent6512dd599d13035f6a90b2009344e02c488f5aae (diff)
restructure project
Diffstat (limited to 'app/mac')
-rw-r--r--app/mac/CMakeLists.txt20
-rw-r--r--app/mac/main.mm60
-rw-r--r--app/mac/scene.hh12
-rw-r--r--app/mac/scene.mm179
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