Initial Setup
For this project we have chosen C++ (C with classes flavor) as the language just because we want to get more comfortable with this language but we could have gone with something different.
First things first. We need a platform layer that allows us to render a window and gives us a buffer that is used to render pixels on the window.
We’re using MacOS and Clang++ to compile our code. But we will add a platform layer for both MacOS and Windows.
MacOS Platform Layer
We wanted to keep this layer as simple as we’re not interested on this part right now. We just want to get started as fast as possible. We 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.
We don’t have too much knowledge building MacOS applications but this should get us started. The code creates an 800x600 pixels window and has a tick function that we can use to render our objects.
Now we just need to compile and run the executable created. For this we have created a simple Makefile that allows us 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 we run the make command, the executable named Rasterizer is added to the ./bin folder. We can now just run it with ./bin/Rasterizer and an empty window should appear.
Now that the platform layer is done we can move on to the core part of the project.
Windows Platform Layer
TODO