Graphics Rasterizer

The Renderer

The first thing I want to do here is to create a Renderer struct that will hold the context of the current renderer used for the rasterization.

For now I need to specify the width and height of the window. I will also need to keep a pointer of the pixels of the screen. These pixels are just color values represented by a uint32_t.

struct Renderer {
    bool ready;
    uint32_t *pixels;
    int width, height;
    int windowWidth, windowHeight;
    int pixelScale;
};

I have also added a pixelScale property too and a width and height to be able to change the size of each pixel. This turns out to be quite handy as I don’t want to renderer too many pixels at the beginning and helps me debugging problems with each pixel value.

Creating a Renderer

To create and start using the renderer, first I need to specify a couple of functions. These will help me to create a renderer instance, destroy it and take care of any allocations, and set the value of the pixels. I also need a way to clear the entire screen.

Renderer Renderer_Create(int w, int h, int pixelScale = 1);
void Renderer_Destroy(Renderer *r);

Here is the implementation of these functions:

Renderer Renderer_Create(int w, int h, int pixelScale) {
    Renderer r = {
        .width = w,
        .height = h,
        .windowWidth = w * pixelScale,
        .windowHeight = h * pixelScale,
        .pixelScale = pixelScale,
    };

    r.pixels = new uint32_t[w * h];
    r.ready = true;

    return r;
}

void Renderer_Destroy(Renderer *r) {
    if (r == nullptr) {
        return;
    }

    delete[] r->pixels;
}

In the Renderer_Create function, I allocate the necessary space for the pixels that is needed and return an instance of the Renderer. This instance will be used from the platform layer to call all the functions of the renderer.

Painting Pixels

Now that I have a renderer entity, I can add some more functions that allow me to update the pixels of the screen. I want to modify pixel by pixel and also I want to have a way to clear the entire screen.

void Renderer_ClearBackground(Renderer *r, uint32_t color = 0xFF000000);
void Renderer_SetPixel(Renderer *r, int x, int y, float z, uint32_t color);

The Renderer_ClearBackground function just sets the color to all the pixels of the screen, effectively overriding the previous values.

The Renderer_SetPixel also accepts a z value that will used later on to do some depth testing to render only the vertices that are closer to the camera.

Here is the implementation:

void Renderer_ClearBackground(Renderer *r, uint32_t color) {
    if (r == nullptr) {
        return;
    }

    for (int i = 0; i < r->width * r->height; i++) {
        r->pixels[i] = color;
    }
}

void Renderer_SetPixel(Renderer *r, int x, int y, float z, uint32_t color) {
    if (r == nullptr) {
        return;
    }

    if (x < 0 || x >= r->width || y < 0 || y >= r->height)
        return;

    int idx = y * r->width + x;

    r->pixels[idx] = color;
}

The Renderer_SetPixel function checks that the coordinates are inside the renderer bounds and sets the color value in the correct pixel that matches the coordinates.

Using the Renderer

Now I need to use this functions in the platform layer. In the tick function I should be able to call the Renderer_ClearBackground function at the beginning, to start with a blank canvas.

...
- (void)tick {

  // Renderer
  Renderer_ClearBackground(&gRenderer, 0x101010);

  // TODO: Start rendering something here

  [view setNeedsDisplay:YES];
}
...
Squared Wave SVG