Initial Setup
For this project I have chosen C++ (C with classes flavor) as the language just because I want to get more comfortable with this language but I could have gone with something different.
First thing first. I need a platform layer that allows me to render a window and gives me a buffer that is used to render pixels on the window.
I’m using MacOS and Clang++ to compile my code. But I will add a platform layer for both MacOS and Windows.
MacOS Platform Layer
I wanted to keep this layer as simple as I’m not interested on this part right now. I just want to get started as fast as possible. I have a simple Objective-C file that creates a window and draws pixels with a tick function that runs every frame.
#import <Cocoa/Cocoa.h>
// TODO: I will create this next
#include "renderer.h"
// TODO: We don't have this yet but we will create it in the next section
static Renderer gRenderer;
@interface RasterizerView : NSView
@end
@implementation RasterizerView
- (BOOL)acceptsFirstResponder {
return YES; // Required to receive keyboard events
}
- (void)drawRect:(NSRect)dirtyRect {
if (!gRenderer.ready)
return;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(
gRenderer.pixels, gRenderer.width, gRenderer.height, 8,
gRenderer.width * 4, colorSpace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGImageRef image = CGBitmapContextCreateImage(ctx);
CGContextDrawImage([[NSGraphicsContext currentContext] CGContext], dirtyRect,
image);
CGImageRelease(image);
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
}
@end
@interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
RasterizerView *view;
NSTimer *timer;
}
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
InitTiming();
const int w = 800, h = 600, pixelSize = 1;
// Internal Resolution: scaled 4x to 800x600
// const int w = 200, h = 150, pixelSize = 4;
// INFO: Initializes the renderer
gRenderer = Renderer_Create(w, h, pixelSize);
// INFO: Creates the window frame 800x600
NSRect frame =
NSMakeRect(100, 100, gRenderer.windowWidth, gRenderer.windowHeight);
window = [[NSWindow alloc]
initWithContentRect:frame
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable
backing:NSBackingStoreBuffered
defer:NO];
view = [[RasterizerView alloc] initWithFrame:frame];
[window setContentView:view];
[window setTitle:@"CPU Rasterizer"];
[window makeKeyAndOrderFront:nil];
// Start render loop
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 60.0
target:self
selector:@selector(tick)
userInfo:nil
repeats:YES];
}
- (void)tick {
// TODO: My render call goes here
[view setNeedsDisplay:YES];
}
- (void)applicationWillTerminate:(NSNotification *)notification {
Renderer_Destroy(&gRenderer);
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
(NSApplication *)sender {
return YES;
}
@end
int main() {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
AppDelegate *delegate = [[AppDelegate alloc] init];
[app setDelegate:delegate];
[app activateIgnoringOtherApps:YES];
[app run];
}
return 0;
}
The previous snippet creates a window using Objective-C and the Cocoa Framework.
I don’t have too much knowledge building MacOS applications but this should get me started. The code creates an 800x600 pixels window and has a tick function that I can use to render my objects.
Now I just need to compile and run the executable created. For this I have created a simple Makefile that allows me to run some make commands:
APP_NAME = Rasterizer
BUILD_DIR = ./bin
RESOURCES_DIR = resources
C_FILES = ./src/*.cpp ./src/*.mm
CFLAGS = -Wall -g -O0 -std=c++17
APP_DEFINES:=
APP_INCLUDES:= -I/usr/local/include -L/usr/local/lib -framework Cocoa -Wl,-rpath,/usr/local/lib
all: build copy_resources
build:
mkdir -p $(BUILD_DIR)
clang++ $(CFLAGS) $(C_FILES) -o $(BUILD_DIR)/$(APP_NAME) $(APP_INCLUDES)
copy_resources:
cp -r $(RESOURCES_DIR) $(BUILD_DIR)/
clean:
rm -rf $(BUILD_DIR)
When I run the make command, the executable named Rasterizer is added to the ./bin folder. I can now just run in with ./bin/Rasterizer and an empty window should appear.
Now that the platform layer is done I can move on to the core part of the project.
Windows Platform Layer
TODO