XNA Shader Programming – Tutorial 26, Bump Mapping Perlin Noise

image

This tutorial will continue from Tutorial 25, where we implemented a noise function. Now that you have the Perlin Noise-function in your shader, it is very simple to implement Bump Mapping.

The object being rendered contains normals on each point of the surface. By calculating the gradient vector for each normal, you can modify the normal to point in a slightly different direction, based on the “bumps” the noise function creates, to create the illusion of a bumpy surface. The Normal must be modified so it point in the right direction based on the bumps, are we going “up” on the side of the bump? Or are we going “down”?

The algorithm

Say you have a function F(x,y,z) that produce a noise based pattern, as inoise() from tutorial 25. By slightly modifying the input of this function, you can calculate the gradient vector. We create a new variable E (EPSILON) that contains a small value like 0.001f. We then use E with the point P as input to the function F for each X, Y and Z:

image

Fx, Fy and Fz can now be used to calculate the gradient vector:

image

Now you got the gradient vector, and can calculate the new vector by subtracting this from the original Normal.

After that, you can implement the lighting model you want the normal way. In this tutorial, we simply use the diffuse lighting model from Tutorial 2.

image

Where N.L is the dotprocut between the Normal vector N and the light vector L.

image

The implementation

You can implement the whole bump mapping algorithm in the shader. We start by implementing the light algorithm in the vertex shader, shouldn’t be new to you:

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 texCoord : TEXCOORD0;
    float3 Normal    : NORMAL;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 texCoord : TEXCOORD0;
    float4 wPosition: TEXCOORD1;
    float3 Light    : TEXCOORD2;
    float3 Normal    : NORMAL;

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
    output.wPosition = mul(input.Position, World);
    output.texCoord = input.texCoord;
    output.Light = normalize(float3(0,0,1));
    output.Normal = normalize(mul(input.Normal, World));

    return output;
}

Next, we store the output from our noise function and use this as the color map:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float3 p = input.wPosition;
    float inz = inoise(p)*0.5+0.5;
    float4 color = float4(inz,inz,inz,1);

Next, we create the variable E that will be used to calculate the gradient vector:

    float E = 0.001f;

Then we take the input point P and add E to each component, storing them in different variables:

float3 pX = input.wPosition;
pX.x += E;
float3 pY = input.wPosition;
pY.y += E;
float3 pZ = input.wPosition;
pZ.z += E;

Next, we create Fx,Fy and Fz by using the exact same function F used to produce the color, but instead of using point P, we use the E-modified point Px, Py and Pz:

float3 bump = float3( inoise(pX)*0.5+0.5,inoise(pY)*0.5+0.5,inoise(pZ)*0.5+0.5);
float3 modNormal = float3( (bump.x-inz) / E, (bump.y-inz) / E, (bump.z-inz) / E);

We then subtract the gradient vector from the input Normal, and normalize this:
float3 Normal = normalize(input.Normal – modNormal);

All that is left is to implement the lighting model we want (in this case, diffuse light), using the new modified Normal:

    float Ai = 0.3f;
    float4 Ac = float4(0.3, 0.0, 0.3, 1.0);
    float Di = 1.0f;
    float4 Dc = float4(1.0, 1.0, 1.0, 1.0);
    return Ai*Ac + color*saturate(dot(input.Light, Normal));
}

That is all there is to bump mapping your noise function. Smilefjes

 

Download: Source (XNA 4.0)

Reference: GPU Gems 1 – Improved Perlin Noise

Posted in Graphics, Tutorial, XNA Shader Tutorial | 2 Comments

XNA Shader Programming – Tutorial 25, Perlin Noise using the GPU

image

This tutorial will intruduce you to procedural textures. If you ever want to generate textures procedurally, you probably stumble upon the Perlin Noise algorithm created by Ken Perlin in 1983.

Why noise?

If you are trying to model natural textures like grass, grain, wood, marble, clouds (+++), you want the textures to contain some irregularity to avoid making the texture look too repeating or perfect. This can be done by using pseudo-random numbers (PRN). You also want to have control over the PRN when using them in your texture generators, so your output won’t be different every time you run your program, or every frame.

Perlin Noise does this. It’s an algorithm that generates a real PRN for every point in space in the range –1 to 1, and it’s controllable (if you know the seed that generates the pattern you want, you can get the same pattern later). The values that the Perlin Noise function returns changes smoothly when moving from a point P1 to another point P2.

Perlin Noise can be used in many other situations as well, like generating landscapes (Minecraft is using this to generate the landscape), fractals, vertex displacements and so on. It’s also used in movies like Tron, LOTR and A Perfect Storm.

Ken Perlin himself has written an article on generating Perlin Noise on the GPU. This article can be found in the book GPU Gems 1, and another one in GPU Gems 2. You can read them for free online here:

http://developer.nvidia.com/content/gpu-gems-part-i-natural-effects
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter26.html

This implementation is based on this and another tutorial found here: http://re-creationstudios.com/shared/PerlinNoiseGPU/, but converted to XNA 4.0.

The algorithm

The algorithm can be learned from the presentation “Making Noise” by Ken Perlin at GDCHardCore on Dec 9, 1999.

Noise can be used in any space Rn. You input a coordinate, and it returns a PRN-value for that coordinate. The Noise function is defined on a regular grid, where each grid point is a whole number.

image

When you feed the input coordinate, the algorithm will look at each of the surrounding 2n grid points. The grid points are located on the grid corners (whole numbers), and the point  P is the fraction, somewhere between the grid points.

image

At each surrounding grid point, you select a “random” gradient vector. The same gradient vector must be used for the same grid-point every time, and have the length of 1.
image

Also, we need the vectors from each grid point to point P, let’s call them gP:

image

By performing the dot product on the gradient vectors and gP, we can find out how much each of them is affecting point P.

Then you interpolate between all of the values computed above to get the final value for point P. If you are in 3D space R3, you will have 8 surrounding grid points, and 7 interpolations to get the final value.

image

 

The first thing that we need is a pre-computed table containing the values 0 – 255 in random order, in two dimensions. This is used to generate one or more PRN at every point P.

We also need a table of gradients. We are implementing noise over R4, and thus needs 16 gradients points.

These will have to be generated on the CPU during loading, as it’s not possible to do it on the GPU with XNA. Also, as these are arrays, and XNA doesn’t support arrays in the shaders, we pass them to our shader as textures.

The shader then use these textures to calculate noise.

Implementaion

First, we create the lookup table for the permutations, and the gradients. Start by creating a new class named PerlinNoise and define it as the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace NoiseGPU
{
    public class PerlinNoise
    {
        GraphicsDevice device;

        public PerlinNoise()
        {
        }
    }
}

Then we define our two tables as local variables to the class:

// permutation table
static int[] permutation = new int[256];

// gradients for 3d noise
static float[,] gradients = 
{
    {1,1,0},
    {-1,1,0},
    {1,-1,0},
    {-1,-1,0},
    {1,0,1},
    {-1,0,1},
    {1,0,-1},
    {-1,0,-1},
    {0,1,1},
    {0,-1,1},
    {0,1,-1},
    {0,-1,-1},
    {1,1,0},
    {0,-1,1},
    {-1,1,0},
    {0,-1,-1}
};

Notice that the permutation table is empty. We calculate this in a function named InitNoiseFunction(int seed). This function takes in a seed (so we can get the same result next time we run the application), and generates our permutations.

public void InitNoiseFunctions(int seed, GraphicsDevice device)
{
    this.device = device;

    Random rand = new Random(seed);

    // Reset
    for (int i = 0; i < permutation.Length; i++)
    {
        permutation[i] = -1;
    }

    // Generate random numbers
    for (int i = 0; i < permutation.Length; i++)
    {
        while (true)
        {
            int iP = rand.Next() % permutation.Length;
            if (permutation[iP] == -1)
            {
                permutation[iP] = i;
                break;
            }
        }
    }
}

Now that we got our two tables ready, we can start creating our textures so we can pass the tables in to our shader:

public Texture2D GeneratePermTexture2d()
{
    Texture2D permTexture2d = new Texture2D(device, 256, 256, true,
                                                                                     SurfaceFormat.Color);
    Color[] data = new Color[256 * 256];
    for (int x = 0; x < 256; x++)
    {
        for (int y = 0; y < 256; y++)
        {
            int A = perm2d(x) + y;
            int AA = perm2d(A);
            int AB = perm2d(A + 1);
            int B = perm2d(x + 1) + y;
            int BA = perm2d(B);
            int BB = perm2d(B + 1);
            data[x + (y * 256)] = new Color((byte)(AA), (byte)(AB),
                                                                    (byte)(BA), (byte)(BB));
        }
    }
    permTexture2d.SetData<Color>(data);
    return permTexture2d;
}

public Texture2D GeneratePermGradTexture()
{
    Texture2D permGradTexture = new Texture2D(device, 256, 1, true,
                                                                     SurfaceFormat.NormalizedByte4);
    NormalizedByte4[] data = new NormalizedByte4[256 * 1];
    for (int x = 0; x < 256; x++)
    {
        for (int y = 0; y < 1; y++)
        {
            data[x + (y * 256)] = new NormalizedByte4(gradients[permutation[x] % 16, 0],
                                                                                         gradients[permutation[x] % 16, 1],
                                                                                         gradients[permutation[x] % 16, 2], 1);
        }
    }
    permGradTexture.SetData<NormalizedByte4>(data);
    return permGradTexture;
}

These two functions simply maps the tables into two textures. The first one generates a 2d table of 256×256 PRN values, and returns an output similar to the image below:
image

The other functions takes the gradients from our array, and store the directions as colors into a 16×1 sized texture. Its output is similar to the image below:
image

Now, over to our shader. I added a new Effect file to the project and changed the vertex shader to look like this:

float4x4 World;
float4x4 View;
float4x4 Projection;

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 texCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 texCoord : TEXCOORD0;
    float4 wPosition: TEXCOORD1;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
    output.wPosition = mul(input.Position, World);
    output.texCoord = input.texCoord;

    return output;
}

It’s out of the box with two exceptions, the texture coordinate and a new variable named wPosition, simply the world position of the vertex. This will be used as the input point P to our noise algorithm (the point P we talked about in the theory section).

Next, we need our two textures and their samplers:

texture permTexture2d;
texture permGradTexture;

sampler permSampler2d = sampler_state
{
    texture = <permTexture2d>;
    AddressU  = Wrap;       
    AddressV  = Wrap;
    MAGFILTER = POINT;
    MINFILTER = POINT;
    MIPFILTER = NONE;  
};

sampler permGradSampler = sampler_state
{
    texture = <permGradTexture>;
    AddressU  = Wrap;       
    AddressV  = Clamp;
    MAGFILTER = POINT;
    MINFILTER = POINT;
    MIPFILTER = NONE;
};

Should be know to you by now. Next, we create a few helper functions:

float3 fade(float3 t)
{
    return t * t * t * (t * (t * 6 – 15) + 10);
}

fade(..) is the interpolation function we are using to interpolate through the grid points. This function is f( t ) = 6t5 – 15t4 +10t3

float4 perm2d(float2 p)
{
    return tex2D(permSampler2d, p);
}

perm2d simply grabs the permutation value at point P.

float gradperm(float x, float3 p)
{
    return dot(tex1D(permGradSampler, x), p);
}

gradperm compute the inner product of the gradient vector, and the vector from the point P to the grid-coordinate.

Then we create the algorithm itself.

float inoise(float3 p)
{
    float3 P = fmod(floor(p), 256.0);    // FIND UNIT CUBE THAT CONTAINS POINT
      p -= floor(p);                      // FIND RELATIVE X,Y,Z OF POINT IN CUBE.
    float3 f = fade(p);                 // COMPUTE FADE CURVES FOR EACH OF X,Y,Z.

    P = P / 256.0;
    const float one = 1.0 / 256.0;
   
    // HASH COORDINATES OF THE 8 CUBE CORNERS
    float4 AA = perm2d(P.xy) + P.z;
 
    // AND ADD BLENDED RESULTS FROM 8 CORNERS OF CUBE
      return lerp( lerp( lerp( gradperm(AA.x, p ), 
                             gradperm(AA.z, p + float3(-1, 0, 0) ), f.x),
                       lerp( gradperm(AA.y, p + float3(0, -1, 0) ),
                             gradperm(AA.w, p + float3(-1, -1, 0) ), f.x), f.y),
                            
                 lerp( lerp( gradperm(AA.x+one, p + float3(0, 0, -1) ),
                             gradperm(AA.z+one, p + float3(-1, 0, -1) ), f.x),
                       lerp( gradperm(AA.y+one, p + float3(0, -1, -1) ),
                             gradperm(AA.w+one, p + float3(-1, -1, -1) ), f.x), f.y), f.z);
}

This functions puts it all together. It first makes a unit cube around the point and the coordinates of the cube. It then computes the face curves for each axis of the point. Then we interpolate Smilefjes

Last, we create the PixelShader function:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float3 p = input.wPosition;
    float inz = inoise(p)*0.5+0.5;
    return float4(inz,inz,inz,1);
}

technique PerlinNoise
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

The only thing that needs an explanation here is the inx variable. As the noise is in the –1 to 1 scale, we need to change this in to a 0 to 1 scale. It’s done by multiplying the value with 0.5 and adding 0.5 to it.

Implementing our application

Now, let’s take this into use. Nothing should be new in this code.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace NoiseGPU
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Model meshObject;
        Matrix projection, view;
        Effect perlinNoiseEffect;

        Texture2D permTexture2d;
        Texture2D permGradTexture;

        PerlinNoise noiseEngine = new PerlinNoise();

        private void DrawModel(Model m, Matrix projection, Matrix view)
        {
            Matrix[] transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);
           

            foreach (ModelMesh mesh in m.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.CurrentTechnique = perlinNoiseEffect.Techniques[“PerlinNoise”];
                    effect.Parameters[“permTexture2d”].SetValue(permTexture2d);
                    effect.Parameters[“permGradTexture”].SetValue(permGradTexture);
                    effect.Parameters[“World”].SetValue(transforms[mesh.ParentBone.Index]);
                    effect.Parameters[“View”].SetValue(view);
                    effect.Parameters[“Projection”].SetValue(projection);
                }
                mesh.Draw();
            }
        }

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = “Content”;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            float aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
            projection =
                Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                aspectRatio, 1.0f, 10000.0f);
           

            noiseEngine.InitNoiseFunctions(3435, graphics.GraphicsDevice);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            meshObject = Content.Load<Model>(“sphere”);
            perlinNoiseEffect = Content.Load<Effect>(“perlinNoiseEffect”);

            foreach (ModelMesh mesh in meshObject.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = perlinNoiseEffect;
                }
            }

            permTexture2d = noiseEngine.GeneratePermTexture2d();
            permGradTexture = noiseEngine.GeneratePermGradTexture();

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name=”gameTime”>Provides a snapshot of timing values.</param>
        double timer = 0;
        protected override void Update(GameTime gameTime)
        {
            timer += gameTime.ElapsedGameTime.Milliseconds/5000.0;
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            view = Matrix.CreateLookAt(new Vector3(100.0f * (float)Math.Sin(timer), 0.0f, 100.0f * (float)Math.Cos(timer)),
                Vector3.Zero, Vector3.Up);

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name=”gameTime”>Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.DarkOliveGreen);

            DrawModel(meshObject, projection, view);

            spriteBatch.Begin(SpriteSortMode.Deferred, new BlendState());
            spriteBatch.Draw(permGradTexture, new Rectangle(0, 0, 256, 32), Color.White);
            spriteBatch.Draw(permTexture2d, new Rectangle(GraphicsDevice.Viewport.Width-256, 0, 256, 256), Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

We render a sphere using the noise effect we created, and also render the two textures we are generating.

Download: Source (XNA 4.0)

Posted in Graphics, Tutorial, XNA Shader Tutorial | 4 Comments

Commodore 64 Programming #10: Multiple interrupts

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

We are not going to introduce many new things in this tutorial, just use what you know to create more than one interrupt. Interrupts can be used to split you screen into different sections.

An important note is that these “interrupts” is your bridge to how it will be done when you get to a more advanced level (will be covered in a later part) and is intended for beginners. These will get you going, but know that there is a better and more correct way to do interrupts. In how I teach, I find it easier to start this way and then move to the correct way when the basic understanding is in place.

Anyways, say you want one part of your screen to render an image, and then once the image is rendered, you want to render some text. That means that you must set up your screen to support both graphic mode and text mode. This can be done with interrupts!

I’m going to create a program that splits the screen into two, where the first part got a purple color and the second part got a green color. We split the screen on every refresh using two interrupts.
The first interrupt does some logic before changing the IRQ vector to point to another “IRQ function” before returning to the main loop. The other interrupt does some logic, triggers another raster interrupt, changes the IRQ vector back to the original and returns to the main loop.

Not much is changed since the previous tutorial. We start with the exact same logic until the main loop starts to run:

processor    6502
org    $0810

; initiate music
lda #$00
tax
tay
jsr $1000

        ;clear screen
jsr $e544

        ; disable interrupts
sei
lda #$7f
sta $dc0d
sta $dd0d
lda #$01
sta $d01a

        ; set text mode
lda #$1b
ldx #$08
ldy #$14
sta $d011
stx $d016
sty $d018

        ; init irq
lda #<irq
ldx #>irq
sta $0314
stx $0315

        ; create rater interrupt
ldy #$00
sty $d012

        ; clear interrupts and ACK irq
lda $dc0d
lda $dd0d
asl $d019
cli

loop:    jmp loop

In the first irq, we set the color of the border and mainscreen, updates the music..

irq:  lda #$04
sta $d020
sta $d021

        jsr $1006

..and then use the same logic as earlier to set the IRQ vector to point to irq2.

        lda #<irq2
ldx #>irq2
sta $0314
stx $0315

We set the next raster interrupt at line 160:

        ldy #160
sty $d012

        asl $d019
jmp    $ea81

The code for the 2nd interrupt is very similar to the first one. We set the color of the border and the screen, and then set the IRQ-vector to point on irq.

irq2:
lda #$05
sta $d020
sta $d021

        lda #<irq
ldx #>irq
sta $0314
stx $0315

        ldy #$00
sty $d012

        asl $d019
jmp    $ea81

Finally, we set the music at location $1000-7e:

    org $1000-$7e
INCBIN “music.sid”

As I said, nothing new here really. Interrups are really handy when it comes to splitting the screen and timing. One thing to remember is that these interrups are not stable, so you might see some jittering or artifacts on the screen. It’s all about timing. We are going to see how to create stable interrupts in a later tutorial.

Note: You are not limited to only have two interrupts. Feel free to play around a bit, make some lines and so on.

An excerise for you:
Try to see if you can get the position of the split to move up and down. It might not be as easy as you thing, but you should be able to do this by now! Smilefjes som blunker

Listing 10.1 – More than one interrupt

processor    6502
org    $0810

; initiate music
lda #$00
tax
tay
jsr $1000

        ;clear screen
jsr $e544

        ; disable interrupts
sei
lda #$7f
sta $dc0d
sta $dd0d
lda #$01
sta $d01a

        ; set text mode
lda #$1b
ldx #$08
ldy #$14
sta $d011
stx $d016
sty $d018

        ; init irq
lda #<irq
ldx #>irq
sta $0314
stx $0315

        ; create rater interrupt at line 0
ldy #$00
sty $d012

        ; clear interrupts and ACK irq
lda $dc0d
lda $dd0d
asl $d019
cli

loop:    jmp loop

irq:  lda #$04
sta $d020
sta $d021

        jsr $1006

        lda #<irq2
ldx #>irq2
sta $0314
stx $0315

        ; Create raster interrupt at line 160
ldy #160
sty $d012

        asl $d019
jmp    $ea81

irq2:
lda #$05
sta $d020
sta $d021

        lda #<irq
ldx #>irq
sta $0314
stx $0315

        ldy #$00
sty $d012

        asl $d019
jmp    $ea81

    org $1000-$7e
INCBIN “music.sid”

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/10-MultipleInterrupts

Posted in Commodore 64 | 30 Comments

Commodore 64 Programming #9: Interrupts and music

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

In this tutorial we will take a look at one of the most important topic when it comes to C-64 programming – Interrupts.

Interrupts are used to pause whatever the machine is doing at a given condition, and do another task. When the interrupt is complete, your program will continue to run where it was interrupted.

An interrupt might be a timer interrupt that happens when a given amount of cycles has passed, a raster interrupt, a collision interrupt and so on. You can have multiple interrupts during a screen refresh. If you have interrupts enabled, and don’t use them to time your program, your application will be jerky and flickering.

There are two types if interrupts. One is Interrupt Request (IRQ), the one we are going to program in this tutorial, and then another one named Non-Maskable Interrupt (NMI). The difference between these is only that you can turn off the IRQ’s, but not the NMI’s (unless you do a trick).

We are also going to introduce two new instructions, SEI and CLI:
– SEI (SEt I(nterrupt) flag) instruction does disable interrupts.
– CLI (CLear I(nterrupt) flag) instruction does enable interrupts.

There is an initialization process when creating interrupts. During this, it’s very important to disable interrupts just to make sure another interrupt won’t ruin the init process. Smilefjes (If not, your application MIGHT crash)

The example in this tutorial will use interrupts to play music. Playing music in a program is very simple. Only a music file (.sid) and 5 lines of code is required. You also need to find some numbers that will be used to locate, init and play the music using a SID-player tool.

You can create your own music, but since I’m nothing near a musician, I downloaded a music and used this in our example. The file is named music.sid, and the title of the song is “Masses Zak”. I got the file from High Voltage SID Collection, a page with a huge archive of C-64 music. Be sure to take a look! Smilefjes

Before we start, download this music file:
Source at GitHub

You also need a tool that can play music files, just to find out some information about the file, like where in the memory it will be stored. Most files will be at $1000, but not all. Smilefjes
The SID player I use is named Sidplay2 for Windows and can be downloaded here.

Now, download and start Sidplay 2, open “music.sid” (can be downloaded above) and the music will start playing. Now click File->Properties to see a long list with information regarding the SID-file:
image

What’s important to note is the Load range, Init address and Play address. These will be used when we init and play our song (d’Oh).

Let the programming begin!

As usual, we start by telling the compiler what processor we are programming for, and where in the memory our program should start.

    processor    6502
org    $0810

But wait, we are starting on $0810 instead of $1000? Why is this? Well, our music file will be loaded into $1000 (Load range), so we simply move the start address for our program to $0810. To run our program, start it in the emulator and write SYS 2064 (decimal of 0810 hex)

Next we initiate the music. This is done but putting the value 00 into the x- and y-registers, and call the subroutine that resets the SID-chip. The properties in the SID file stated that the init routine for the music is at $1000, so that’s what we want to do. Smilefjes

             lda #$00
tax
tay
jsr $1000

Now we are going to initiate the interrupts. First we need to turn off the interrupts:
             sei

Then we put the value 7f into $dc0d and $dd0d to disable the CIA I, CIA II and VIC interrupts (timer, keyboard,…, interrupts)
             lda #$7f
sta $dc0d
sta $dd0d

We also need to enable raster interrupts. We do this by inserting 01 into $d01a.
             lda #$01
sta $d01a

Next we tell the VIC that we want to enter single-color text mode. Inserting 1b into $d011 means “Enter text-mode”, and inserting 08 into $d016 means “Use single-color”. We also tell the VIC that our screen RAM is at $0400 and that we want to use the default charset by inserting 14 into $d018 (see earlier tutorials for more information about how this works).
             lda #$1b
ldx #$08
ldy #$14
sta $d011
stx $d016
sty $d018

Now we are at the meat of this tutorial. What we will do next is to load the interrupt handlers codes lower and high part into the interrupt vector at $0314-$0315. “irq” is the label where the code for our interrupt is located, so all we do is to insert a pointer to this into $0314-$0315:
             lda #<irq
ldx #>irq
sta $0314
stx $0315

Then we need to create the trigger for out interrupt at “irq”. We want a raster interrupt at any line (in this example $7e) to trigger the interrupt.
             ldy #$7e
sty $d012

Then we clear pending interrupts (the CIA 1, CIA 2 and VIC interrupts).
             lda $dc0d
lda $dd0d
asl $d019

Now that the interrupt is initiated, we can enable interrupts and start with the program logic. In this example, we are only running an infinite loop:
             cli
loop:     jmp loop     ; infinite loop

Next is the code for our interrupt. What this does is to first run a sub routine at $1006 (the play SID-file routine for music.sid (remember the properties of the sid file)):
irq:       jsr $1006

Then we ACK the interrupt with asl $d019. This is done because we don’t want the interrupt to be called again right after we return from it.
             asl $d019

Then we jump to a subroutine that restores the stack and returns from the interrupt. If you want to save 3 cycles, you could write this manually, but for simplicity, we jump to $ea81 (see below for what you can replace this with if you want to write the code yourself):
             jmp    $ea81

The last thing we do is loading the music into $1000. But a SID file got an offset of $7e so we need to subtract this from $1000 so the file is correctly placed in memory.

    org $1000-$7e
INCBIN “music.sid”

That’s if for basic interrupt. We will be more advanced in a later tutorial as interrupts are really important when it comes to C-64 programming.

A complete listing of our example is in listing 9.1.

Listing 9.1 – Interrupts and music

processor    6502
org    $0810

             lda #$00
tax
tay
jsr $1000
sei
lda #$7f
sta $dc0d
sta $dd0d
lda #$01
sta $d01a
lda #$1b
ldx #$08
ldy #$14
sta $d011
stx $d016
sty $d018
lda #<irq
ldx #>irq
ldy #$7e
sta $0314
stx $0315
sty $d012
lda $dc0d
lda $dd0d
asl $d019
cli
loop:    jmp loop
irq:      jsr $1006
asl $d019
jmp    $ea81

    org $1000-$7e
INCBIN “music.sid”

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/9-InterruptsMusic

Posted in Commodore 64 | 19 Comments

Commodore 64 Programming #8: Text and custom charsets

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

In the previous tutorial, we learned how to create and render a bitmap, and how to enter bitmap-mode.

In this tutorial, we are going to use almost the same concepts, but instead of rendering bitmaps, we are rendering text. We are also going to use a custom charset instead of the pretty boring charset that’s default.

The default charset look like this:
image

First of all, I’m not good at creating fonts so I won’t teach you how to do this. Instead we are going to download a charset that is named Scrap Writer III 17. Don’t know who created it but if anyone knows, please let me know so I can add proper credits.

The Scrap Writer III 17 charset look like this:
image

Donwload the charset here:

scrap_writer_iii_17.64c

Download other charsets from this page: http://kofler.dot.at/c64/ but remember, some of them might be copyrighted.

Rendering text

Rendering text is quite simple. All you need is to either use the default charset or a custom charset. Also, you will need the text you want to render.

Let’s write our program.

First, we want to clear the screen. You could use the method we created earlier, but to keep this example to the point, I’m going to use a function that is included on the Commodore 64 and located at $e544. This routine clears the screen.

Also, we want to set the screen color and the border color to something greenish.

    processor   6502
org    $1000

                 jsr $e544
lda #$0d
sta $d020
lda #$05
sta $d021

Then we load the custom charset, using the same method as in the previous tutorial about rendering bitmaps. Our screen memory is at $0400 and charset at $2000.
                 lda #$18
sta $d018

Now it’s time for the meat of this tutorial. The loop that writes the text! First, we set the x-register to zero. Then we load the x’th character of msg (declared in the bottom) into the accumulator.

                ldx #$00
write:     lda    msg,x

Once it’s loaded, we jump to a subroutine that writes text to the screen. This routine is located at $ffd2. All it does is to write the value in the accumulator to the screen.
                jsr    $ffd2

The text we want to render is 54 character long. We loop through each of the characters and write it to the screen.
                inx
cpx    #54
bne    write

Next we are setting the color of our characters. This could be done in the loop above, but just to split the different functionality, I decided to create another loop that does this. The color is stored at $d800 and so on. We do this for each of the 54 characters.

                ldx #$00
setcolor: lda #$07
sta $d800,x
inx
cpx #$54
bne setcolor

In the end, we create an infinite loop so we can see what we rendered.
loop:        jmp loop

Then we need to load our data. We create msg that contain the text we want to render

msg        .byte “C64 programming tutorial by digitalerr0r of Dark Codex”

And then we include our custom charset

    org    $1ffe
INCBIN “scrap_writer_iii_17.64c”

And that’s it for rendering text. If you run this example, you will see the same result as below.

image

Listing 8.1 – Rendering text

    processor   6502
org    $1000

                 jsr $e544
lda #$0d
sta $d020
lda #$05
sta $d021
lda #$18
sta $d018

                 ldx #$00
write:      lda    msg,x
jsr    $ffd2
inx
cpx    #54
bne    write

                 ldx #$00
setcolor:  lda #$07
sta $d800,x
inx
cpx #$54
bne setcolor
loop:        jmp loop

msg        .byte “C64 programming tutorial by digitalerr0r of Dark Codex”

    org    $1ffe
INCBIN “scrap_writer_iii_17.64c”

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/8-TextCharsets

Posted in Commodore 64 | 11 Comments

Commodore 64 Programming #7: Creating and rendering bitmaps

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

In this tutorial we are going to cover two things. First we will look at one way of creating bitmaps/images for our C-64 programs by using a tool for Windows. Next we will load the image we created into our own program and render it.

Creating bitmaps

Before you can draw your bitmap, you will need a tool. The tool I like to use is named Timanthes v3, and can be downloaded below.

imageDownload Timanthes 3.0 beta by Mirage of Focus:  http://noname.c64.org/csdb/release/?id=75871

Load Timanthes and you will see an environment much similar to other painting programs in front of you. When painting for the Commodore 64 you must be aware of a few limitations. The Commodore 64 supports a few different graphic modes. The one introduced here is a pretty common one and will work as a basis to understand the rest of them.

The format we are implementing is named MultiColor and got a resolution of 160×200 pixels, with the support of 16 colors. As you can see, the resolution of the images is smaller than the screen. One pixel in the image represents 2×1 pixels when rendered on the screen. Another important attribute with this mode is that the screen is divided into 40×25 attribute cells, where each attribute cell is 4×8 pixels in size. Each is also limited to contain the maximum of 4 colors.

Confused? Let me break it down for you:

The image is 160×200 in size…
image

..where one pixel in the image represents 2×1 pixels in screen size:
image In this picture, I drew ONE line across 8 pixels, and another line down 8 pixels. Notice that the width of one pixel spans across two pixels when rendered.

I also mentioned that this image mode splits the image into 40×25 attribute cells:

image

where each of the cells contains 4×8 pixels (8×8 in real):
image This is one cell. It contains 4 pixels you can draw in the x-axis and then 8 pixels you can draw in the y-axis. One pixel in the x-axis represents 2 pixels in screen size.

Also, one cell can only contain the maximum of 4 colors, including the background. If you try to draw more than 4 colors in Timanthes, a red pixel will show on the colors that exceed the limit, and must be corrected.

Now, the first thing you will see when loading Timanthes is that you got a drawing area and some floating windows. Move these into a position you like. Now, in the layers window, notice that you got two layers. This is how it should be when exporting your image to a prg file. You NEED to have two layers. I usually draw on the 2nd layer.

Also, on the right side of the layer preview image, you can see what type of layer your dealing with:

image

This is where you select what kind of graphic mode you want the image in. Select Layer 2 (it’s probably already selected) and click the properties button:

image

You will see a popup window:

image

In Mode, select “Bitmap multicolor” and click OK.

Now, select a color from the Colors window. This is the palette. This mode support 16 different colors. You can modify each color from this window. But remember, 16 in total is the limit. Smilefjes

image

Now, draw any image. My result was this, the logo of my group in the demoscene. You can download the file below.

image

image Click the file below to download dcc.PRG

DOWNLOAD

Now it’s time to export your image from Timanthes. Click File and Export As, the type must be .PRG and you can name it whatever you want. I named mine dcc.prg. Press OK and a new popup will show:

image

You can leave it like this for this example, but these values are the values we are going to use when loading the image into memory and displaying it!

A list of other graphic modes can be found here: http://www.studiostyle.sk/dmagic/gallery/gfxmodes.htm

Loading and displaying the image in your program

Keep in mind that one image takes about 2000 bytes in memory, so it has to be loaded into $2000, $4000, $6000 and so on, and not in $2456.

Let’s write our code. A complete listing can be seen below.

First of all, we set the background color to the one that the image is using. When you exported the image, you noticed that the background is stored in $4710. Load the value from here into the d020 and d021 memory.

processor 6502
org $1000

     lda $4710
sta $d020
sta $d021

Now we will create a loop that copies the data to screen RAM that starts at $0400. The data (in one way, we use characters (the cells), but more on this later) for our image is located at $3f40 (as we noticed when we exported the image). We use the same method for copying as in the earlier example where we cleared the screen.

First, set the x-register to zero, and start the copying:

     ldx #$00
loaddccimage:
lda $3f40,x
sta $0400,x
lda $4040,x
sta $0500,x
lda $4140,x
sta $0600,x
lda $4240,x
sta $0700,x

Also, we must copy the color RAM for our image located at $4328 (specified when exporting the image) to $d800. We add this to the loop:

     lda $4328,x
sta $d800,x
lda $4428,x
sta $d900,x
lda $4528,x
sta $da00,x
lda $4628,x
sta $db00,x

One last thing has to be done in the loop, and that is to increase x. If x does not equal zero, branch to the loaddccimage label.

     inx
bne loaddccimage

The loop will now loop until x is zero, copying all data into the correct memory. Once this is done, the image is loaded and ready to display! Smilefjes

The next thing we need to do is to tell “the system” that we want to enter bitmap mode, and that the mode is a multicolor mode.

$d011 must be set to what mode we want to go into. To got into bitmap mode, we set $d011 to 3b:

     lda #$3b
sta $d011

Now we are in bitmap mode.

Next, we must turn on multicolor-mode. This is done by putting the value in $d016 to 18.

     lda #$18
sta $d016

Now, we are in bitmap multicolor-mode! Smilefjes

The last thing we need to do is to tell the VIC that the screen RAM is at 0400 and that the bitmap is at $2000. If we put in the value 18, the first numer (1) is where we want the screen RAM, and the last numer is where the bitmap is. How does this work? First, we know that out screen RAM is located at 0400 and is 0400 bytes long. We count how many times we need to “add” 400 bytes to reach to the desired screen RAM from 0000. So, 0000+0400 = 0400. Thats one time. Next, we must count how many times until we reach 2000. Thats 8 times. To set that the screen RAM is at 0400 and the bitmap is at 2000, we $d018 to #$18:

     lda #$18
sta $d018

This might sound confusing. The screen memory is 400 bytes long, so it has to start on a memory address that is a multiple of 400. If you don’t change where the screen memory is, it’s located at $0400 by default like in this example.

Now, let’s add an infinite loop so that our program won’t just exit once the image is rendered:

loop:
jmp loop

That’s it for loading the image. One final thing remains, and that is to include our image file and put it into the memory. When we exported, we specified that we wanted to have the image at the memory location $2000. Now, a PRG file got a header that is 2 bytes long. That means that we want to include the file in location $2000-2 bytes = $1FFE:

org    $1FFE
INCBIN “dcc.PRG”

If you compile and run this, the emulator will display the image as seen below. Smilefjes

image

A complete listing of our code can be found in listing 7-1.

Listing 7.1 – Complete listing for rendering your multicolor image

    processor   6502
org    $1000

            lda $4710
sta $d020
sta $d021
ldx #$00
loaddccimage:
lda $3f40,x
sta $0400,x
lda $4040,x
sta $0500,x
lda $4140,x
sta $0600,x
lda $4240,x
sta $0700,x
lda $4328,x
sta $d800,x
lda $4428,x
sta $d900,x
lda $4528,x
sta $da00,x
lda $4628,x
sta $db00,x
inx
bne loaddccimage

           lda #$3b
sta $d011
lda #$18
sta $d016
lda #$18
sta $d018

loop:
jmp loop

    org    $1FFE
INCBIN “dcc.PRG”

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/7-Bitmaps

Posted in Commodore 64 | 21 Comments

Commodore 64 Programming #6: Raster lines

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

A raster line is the line that is being redrawn on the screen. You can create a lot of cool effects and implement smart logic that happens when the video signal reaches a specific raster line of your choice.

The screen is redrawn 50 times per second, from top to bottom, from the left to the right. This can be used to synchronize your logic with the screen refresh (and also with the clock!).

The current raster line is stored in the memory location $d012.

Let’s go back to our example in tutorial #1 where we changed the background color on the main area, using the following code:

    processor   6502
org    $1000

loop:    inc $d021
jmp loop

This resulted in something similar to the screen below:

image

Now, let’s implement the same example, but synced with the screen refresh. The code is really short, so I’ll just give you the complete listing.

        processor   6502
org    $1000

loop:     lda $d012
cmp #$ff
bne    loop
inc $d021
jmp loop

What this code does is to first load the value of d012 into the accumulator. And we check if it equals the value of ff (could be anything you want really).

Note: The screen size goes from 0 – 319, and the byte in d012 only got the range of 0 – 255. To get the rest of the screen, you can use the high bit in d011. If this is set, the value in d012 will contain the raster lines after 255. Smilefjes In other words, d011 is the 8th bit of d012.

Now, if you run the code above, you will get a screen that flashes different colors 50 times per second, without the color distortion that is created due to interrupts and timing.

image

Raster interrupts covered in tutorial 10 is a really important topic. It can be used to create a lot of cool effects like rendering more than 8 hardware sprites, split your screen into sections (one displaying hiQ graphics, other part displaying a font) and so on.

Have fun! Smilefjes

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/6-Raster%20Lines

Posted in Commodore 64 | 13 Comments

Commodore 64 Programming #5: Clearing the screen

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

In the previous tutorials we created stuff that rendered a sprite and changed the color on things. In this tutorial, we will clear the screen to a black color.

To clear the screen, we should set the color of the border and the main screen to a given color, like black, and then we must clear the characters on the screen. This can be done by setting them to the character SPACE.

To do this, first we put the index for black into the accumulator, and store this into the border and the main screen.

    processor   6502
org    $1000

        lda #$00
sta $d020
sta $d021

Next we copy the value in the accumulator into the x-register.
   tax

Then we put the value of #$20 in to the accumulator. #$20 is the value of the SPACE character.
  lda #$20

Now we are ready to put the value inserted in the accumulator into every character on the screen memory. This will be done by creating a loop that goes through the entire screen memory. The screen memory is located at $0400 and ends at $07FF.

Linus Åkerlund of Fairlight shared a smart way of looping through the screen memory. Before the loop starts, x contains the value 0. If we decrement it, it will become FF (255), and then go all the way down to 0 again, setting all the characters to space in the range 0400-04FF, 0500-05FF, 0600-06FF and 0700 to 07FF.

loop:   sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
dex
bne loop

The last instruction is new; bne will jump (branch) to loop if X is not zero. That means that when we start, X will be 0, and right before the bne operation, we will decrease it so it contains FF. This means that the loop will go all the way from FF to 00 before exiting the loop.

Listing 5.1 – Clear loop

    processor   6502
org    $1000

        lda #$00
sta $d020
sta $d021
tax
lda #$20
loop:   sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
dex
bne loop

That’s it for clearing the screen. When the clear loop is done, you can continue doing what else you want the program to do.

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/5-Clear%20Loop

Posted in Commodore 64 | 9 Comments

Demo-source from The Gathering 2011

This is the demo-source created from the presentation I had at The Gathering 2011. Download by clicking the zip-file below:

http://cid-2b7007e9ec2ae37b.office.live.com/embedicon.aspx/.Public/WindowsGame1.rar

image

Screenshot from the app

The game MageDefender is also on the way, but delayed due to some uploading issues. Sad smile

Posted in Uncategorized | Leave a comment

Commodore 64 Programming #4: Rendering a sprite

image

Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.

In this tutorial we will take a quick look at how to render a sprite on the very nice standard C-64 background. Check out Sprite Pad for Windows to create sprites but for now, feel free to download the following sprite file: sprite1.prg

In the GitHub repo, there is another Sprite named sprite2.spr and the source in GitHub is using, as well as the SpritePad project file. Feel free to try both to see the difference.

Sprites are 2d images that you can move over a background, like a player character, a sun, a tree, an enemy and so on. You can have up to 8 sprites rendered at the same time.To render a sprite, you will have to enable it, give it a position, and have a pointer to its data.

Let’s do it step by step. First of all, we need to tell the compiler what processor we are writing our program for, and the entry point for it:

    processor   6502
org    $1000

Pointer to sprite data

Next, we need to create a pointer to our sprite data located at $2000 (you can put them in another location, but in this tutorial, we will put the data in $2000). The sprite pointers are the 8 bytes at the end of the screen memory (0400 – 0800):

Screen memory Sprite 1 Sprite 2 Sprite 3 Sprite 4 Sprite 5 Sprite 6 Sprite 7 Sprite 8
0400 – 0800 07f8 07f9 07fa 07fb 07fc 07fd 07fe 07ff

One sprite takes 64 bytes of data, and 6410 (dec system) equals to 4016 (hex). If we set the pointer for sprite 1 (07f8) to #$00, the data for the sprite will be located at 0000. If we set it to #$01, the data will be located at 0040, if we set it to 2, the data will be located at 0080, if we set it to 3, it will be located at 00C0. How do I count this? 4016 * #number in pointer16 = where the memory for the sprite is located. Our sprite data will be located at $2000. So what value will we need to insert into 07f8? 4016 * 8016 = 200016, in other words, 8016.

We load the value 80 into A, and insert A into 07f8:

    lda #$80
sta $07f8

Enable sprites

Next, we must turn on the sprite. You can use up to 8 sprites, and one byte FF equals 1111 1111, 8 bits. Each of these bits controls if a sprite is enabled or not, and is located in the $d015 block. We only want to enable sprite 1, so we set the first bit to 1 and the rest will remain 0.

This can be done in two ways, either by inserting the hex value, or the binary value. If you insert the hex value #$01, the result will be 0000 0001 and sprite 1 is enabled. If you insert #$02, the result in binary will be 0000 0010 and sprite 2 will be enabled. But if you insert #$03, the result will be 0000 0011, enabling both sprite 1 and 2!

You can also directly use the binary value by using #%0000001,#%0000010 and #%0000011.

We will use the hex value, and enable sprite 1:
    lda #$01
sta $d015

(Or like this if you want to use binary:

    lda #%0000001
sta $d015
)

Set the XY position of the sprite

Next, we must set the XY-coordinates for our sprite. The coordinate memory is located from $d000. Both the X and the Y coordinate will need at least one byte each. To handle this, the first sprites X coordinate is located in $d000, and its Y coordinate is located at $d001. The 2nd sprites X coordinate is located at $d002, and its Y coordinate at $d003. The thirds X is at $d004, and Y at $d005, and so on. If we load the value #$80 into $d000 and $d001, the first sprite will be located at coordinate #$80,#$80 = 128,128 in pixels.
    lda #$80
sta $d000
sta $d001

The last thing we do is to actually load our sprite into the memory at $2000:
* = $2000
INCBIN “sprite1.prg”

Now, what we did was to first set the pointer to the data for our sprite, then we enabled the sprite, then sat the coordinates, and in the end loaded the data for our sprite.

Listing 4.1 – Complete listing for sprite example.

    processor   6502
org    $1000

         lda #$80
sta $07f8
lda #$01
sta $d015
lda #$80
sta $d000
sta $d001

loop: jmp loop

* = $2000
INCBIN “sprite1.prg”

If you take this application and compile it, you will see the result below:
image

..and don’t ask what that sprite is, I’m not a designer! Open-mouthed smile
Is your sprite different? I changed it a bit, so it should now just be a P.

That’s the end of this tutorial.. or is it? Hey? How wide is the C-64 screen? 320. And how large is one byte? 255.. and? Well, if 255 is the max we can insert into the sprites X coordinate, how can we move it all across the screen? Hmm.. good question.

Moving a sprite across the 255 limit

The answer is pretty simple. At the memory location $d010, we got 8 more bits to set. If the first bit in $d010 is set to 1, the position of sprite 1s X coordinate is:

256 + the value in d000

And this is the same for all of the 8 sprites.
So there you have it, see you next time Smile

Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/4-Rendering%20a%20Sprite

Posted in Commodore 64 | 21 Comments