summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-01-13 22:03:10 +0100
committerHannes Schulze <haschu0103@outlook.de>2025-01-13 22:03:10 +0100
commit02f963a4bab95b3a328b6a792c9a7e027ade0e26 (patch)
tree70b73691547e756e77eb30f29d7ea16811bae1e3
parent4366e924e44d4294b9791533486f3ab81317ec71 (diff)
implement display
-rw-r--r--.clang-format3
-rw-r--r--CMakeLists.txt4
-rw-r--r--src/CMakeLists.txt7
-rw-r--r--src/main.mm28
-rw-r--r--src/nes/CMakeLists.txt9
-rw-r--r--src/nes/display.cc16
-rw-r--r--src/nes/display.hh33
-rw-r--r--src/nes/nes.cc13
-rw-r--r--src/nes/nes.hh19
-rw-r--r--src/nes/util/CMakeLists.txt4
-rw-r--r--src/nes/util/rgb.hh33
-rw-r--r--src/scene.hh12
-rw-r--r--src/scene.mm96
-rw-r--r--src/view-controller.hh6
-rw-r--r--src/view-controller.mm15
15 files changed, 265 insertions, 33 deletions
diff --git a/.clang-format b/.clang-format
index a8bb62b..32cf393 100644
--- a/.clang-format
+++ b/.clang-format
@@ -40,6 +40,7 @@ ContinuationIndentWidth: 4
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
+NamespaceIndentation: All
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
@@ -64,7 +65,7 @@ PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
-PointerAlignment: Right
+PointerAlignment: Left
ReflowComments: true
Cpp11BracedListStyle: false
SortIncludes: Never
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 34552bf..5bfbf5d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,8 +7,8 @@ project(
set(CMAKE_CXX_STANDARD 17)
add_executable(nes)
-
add_subdirectory(src)
find_library(FRAMEWORK_COCOA Cocoa REQUIRED)
-target_link_libraries(nes PRIVATE ${FRAMEWORK_COCOA}) \ No newline at end of file
+find_library(FRAMEWORK_SPRITEKIT SpriteKit REQUIRED)
+target_link_libraries(nes PRIVATE ${FRAMEWORK_COCOA} ${FRAMEWORK_SPRITEKIT}) \ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 34c0ed3..68ba329 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,5 +1,10 @@
+add_subdirectory(nes)
+
target_sources(
nes
PRIVATE
main.mm
- view-controller.mm) \ No newline at end of file
+ scene.hh
+ scene.mm)
+
+target_include_directories(nes PRIVATE .) \ No newline at end of file
diff --git a/src/main.mm b/src/main.mm
index a526e66..5207e62 100644
--- a/src/main.mm
+++ b/src/main.mm
@@ -1,36 +1,48 @@
+#import "scene.hh"
+
#import <Cocoa/Cocoa.h>
-#import "view-controller.hh"
+#import <SpriteKit/SpriteKit.h>
int main(int argc, char** argv)
{
NSApplication* app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
- NSMenuItem *quitItem = [[NSMenuItem alloc] init];
+ NSMenuItem* quitItem = [[NSMenuItem alloc] init];
[quitItem setTitle:@"Quit NES"];
[quitItem setKeyEquivalent:@"q"];
[quitItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand];
[quitItem setTarget:app];
[quitItem setAction:@selector(terminate:)];
- NSMenu *menu = [[NSMenu alloc] init];
+ NSMenu* menu = [[NSMenu alloc] init];
[menu addItem:quitItem];
- NSMenuItem *item = [[NSMenuItem alloc] init];
+ NSMenuItem* item = [[NSMenuItem alloc] init];
[item setSubmenu:menu];
- NSMenu *mainMenu = [[NSMenu alloc] init];
+ NSMenu* mainMenu = [[NSMenu alloc] init];
[mainMenu addItem:item];
[app setMainMenu:mainMenu];
- NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0., 0., 640., 480.)
+ Scene* scene = [[Scene alloc] init];
+ CGSize minSize = [scene size];
+
+
+ SKView* view = [[SKView alloc] init];
+ [view setIgnoresSiblingOrder:YES];
+ [view setShowsFPS:YES];
+ [view presentScene:scene];
+
+ NSWindow* 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 setContentViewController:[[ViewController alloc] init]];
- [window setContentSize:NSMakeSize(640., 480.)];
+ [window setContentAspectRatio:minSize];
+ [window setContentMinSize:minSize];
+ [window setContentView:view];
[window makeKeyAndOrderFront:nil];
[app activateIgnoringOtherApps:YES];
diff --git a/src/nes/CMakeLists.txt b/src/nes/CMakeLists.txt
new file mode 100644
index 0000000..560dcdd
--- /dev/null
+++ b/src/nes/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_subdirectory(util)
+
+target_sources(
+ nes
+ PRIVATE
+ nes.hh
+ nes.cc
+ display.hh
+ display.cc) \ No newline at end of file
diff --git a/src/nes/display.cc b/src/nes/display.cc
new file mode 100644
index 0000000..4ff1d5f
--- /dev/null
+++ b/src/nes/display.cc
@@ -0,0 +1,16 @@
+#include "nes/display.hh"
+#include "nes/util/rgb.hh"
+
+namespace nes
+{
+ auto display::clear(rgb const color) -> void
+ {
+ for (auto y = unsigned{ 0 }; y < height; ++y)
+ {
+ for (auto x = unsigned{ 0 }; x < width; ++x)
+ {
+ set(x, y, color);
+ }
+ }
+ }
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/display.hh b/src/nes/display.hh
new file mode 100644
index 0000000..14ccff1
--- /dev/null
+++ b/src/nes/display.hh
@@ -0,0 +1,33 @@
+#pragma once
+
+namespace nes
+{
+ class rgb;
+
+ /// Abstraction of the display logic.
+ class display
+ {
+ public:
+ static constexpr auto width = unsigned{ 256 };
+ static constexpr auto height = unsigned{ 240 };
+
+ virtual ~display() = default;
+
+ display(display const&) = delete;
+ display(display&&) = delete;
+ auto operator=(display const&) -> display& = delete;
+ auto operator=(display&&) -> display& = delete;
+
+ /// Switch between front and back buffers.
+ virtual auto switch_buffers() -> void = 0;
+ /// Update the pixel at the given position in the back buffer.
+ virtual auto set(unsigned x, unsigned y, rgb value) -> void = 0;
+ /// Load the pixel value at the given position in the back buffer.
+ virtual auto get(unsigned x, unsigned y) const -> rgb = 0;
+ /// Clear the entire back buffer with a color.
+ virtual auto clear(rgb color) -> void;
+
+ protected:
+ explicit display() = default;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/nes.cc b/src/nes/nes.cc
new file mode 100644
index 0000000..c503d65
--- /dev/null
+++ b/src/nes/nes.cc
@@ -0,0 +1,13 @@
+#include "nes/nes.hh"
+#include "nes/display.hh"
+#include "nes/util/rgb.hh"
+
+namespace nes
+{
+ nes::nes(display& display)
+ : display_{ display }
+ {
+ display.clear(rgb::from_hex(0xFF0000));
+ display.switch_buffers();
+ }
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/nes.hh b/src/nes/nes.hh
new file mode 100644
index 0000000..ab975f0
--- /dev/null
+++ b/src/nes/nes.hh
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace nes
+{
+ class display;
+
+ class nes
+ {
+ display& display_;
+
+ public:
+ explicit nes(display&);
+
+ nes(nes const&) = delete;
+ nes(nes&&) = delete;
+ auto operator=(nes const&) -> nes& = delete;
+ auto operator=(nes&&) -> nes& = delete;
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/nes/util/CMakeLists.txt b/src/nes/util/CMakeLists.txt
new file mode 100644
index 0000000..c2255dd
--- /dev/null
+++ b/src/nes/util/CMakeLists.txt
@@ -0,0 +1,4 @@
+target_sources(
+ nes
+ PRIVATE
+ rgb.hh) \ No newline at end of file
diff --git a/src/nes/util/rgb.hh b/src/nes/util/rgb.hh
new file mode 100644
index 0000000..d62c70c
--- /dev/null
+++ b/src/nes/util/rgb.hh
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <cstdint>
+
+namespace nes
+{
+ /// An RGB value.
+ class rgb
+ {
+ public:
+ explicit rgb() = default;
+
+ explicit rgb(std::uint8_t const r, std::uint8_t const g, std::uint8_t const b)
+ : r{ r }
+ , g{ g }
+ , b{ b }
+ {
+ }
+
+ static auto from_hex(std::uint32_t const hex) -> rgb
+ {
+ auto res = rgb{};
+ res.r = (hex & 0x00FF0000) >> 16;
+ res.g = (hex & 0x0000FF00) >> 8;
+ res.b = (hex & 0x000000FF) >> 0;
+ return res;
+ }
+
+ std::uint8_t r{ 0 };
+ std::uint8_t g{ 0 };
+ std::uint8_t b{ 0 };
+ };
+} // namespace nes \ No newline at end of file
diff --git a/src/scene.hh b/src/scene.hh
new file mode 100644
index 0000000..967a4fe
--- /dev/null
+++ b/src/scene.hh
@@ -0,0 +1,12 @@
+#pragma once
+
+#import <SpriteKit/SpriteKit.h>
+
+@interface Scene : SKScene
+
+- (instancetype)init 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/src/scene.mm b/src/scene.mm
new file mode 100644
index 0000000..944d01a
--- /dev/null
+++ b/src/scene.mm
@@ -0,0 +1,96 @@
+#import "scene.hh"
+#import "nes/nes.hh"
+#import "nes/display.hh"
+#import "nes/util/rgb.hh"
+#import <memory>
+
+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 texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height)];
+ [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_;
+}
+
+- (instancetype)init
+{
+ self = [super initWithSize:CGSizeMake(nes::display::width, nes::display::height)];
+ if (self)
+ {
+ SKSpriteNode* node = [[SKSpriteNode alloc] init];
+ [node setAnchorPoint:CGPointZero];
+ [node setSize:[self size]];
+
+ [self setScaleMode:SKSceneScaleModeAspectFit];
+ [self addChild:node];
+
+ self->display_ = std::make_unique<display_impl>(node);
+ self->nes_ = std::make_unique<nes::nes>(*self->display_);
+ }
+ return self;
+}
+
+- (void)update:(NSTimeInterval)currentTime
+{
+ [super update:currentTime];
+}
+
+@end \ No newline at end of file
diff --git a/src/view-controller.hh b/src/view-controller.hh
deleted file mode 100644
index a89a0c4..0000000
--- a/src/view-controller.hh
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-#import <Cocoa/Cocoa.h>
-
-@interface ViewController : NSViewController
-@end \ No newline at end of file
diff --git a/src/view-controller.mm b/src/view-controller.mm
deleted file mode 100644
index 5f24326..0000000
--- a/src/view-controller.mm
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "view-controller.hh"
-
-@implementation ViewController
-
-- (void)loadView
-{
- [self setView:[[NSView alloc] init]];
-}
-
-- (void)viewDidLoad
-{
- [super viewDidLoad];
-}
-
-@end \ No newline at end of file