Here's how I did it :
I have a camera which is nothing more than a vector, from its position I do find which tiles needs to be drawn; using tile size and viewport size.
In this example less than 10% of the level is drawn :

The whole level is only using 3 textures that are 32x32 pixels, in comparison to your huge texture. However there are some considerations you should care of if using this method : the level generation. Since it's not a huge texture anymore, you can either code a text file that would be the level, each character representing a tile. Or you can use a level editor for this purpose. A quick search for 'mac level editor' brings Tiled.
Here is an excerpt of what I did, that's the part that does find what to draw and draws only that.
The code should be self-explanative, if not, ask.
namespace x
{
public class LevelComponent : DrawableGameComponent
{
public List<Texture2D> Textures { get; private set; }
public List<Sprite> Sprites { get; private set; }
private readonly List<Bounds> _bounds;
private Vector2 _camera;
private Vector2 _screenCenter;
private Vector2 _screenSize;
private Vector3 _size; // level size as tiles
private Vector2 _sizeAsPixels; // level size ...
private SpriteBatch _spriteBatch;
private Vector2 _sizeOfTile;
private Tile[,,] _tiles;
private int _tilesRendered;
public override void Update(GameTime gameTime)
{
/* We will center on the player just like in Super Mario Bros.
* Also, we clamp the camera to level bounds. */
Vector2 newPos = Players[0].Position - _screenCenter;
Vector2 min = Vector2.Zero;
Vector2 max = SizeAsPixels - _screenSize;
_camera = Vector2.Clamp(newPos, min, max);
/* We get level size as tile units. */
float w = Game.GraphicsDevice.Viewport.Width/SizeOfTile.X;
float h = Game.GraphicsDevice.Viewport.Height/SizeOfTile.Y;
var max1 = new Vector2(SizeOfTile.X, SizeOfTile.Y);
/* Now we are going to find out which level region
* we just want to render otherwise it'd be unpractical to render the whole level.
* Example :
* - If level is 32 * 32 * 3 layers there are 3072 tiles,
* - Now if our viewport is say 5*5, we only render 5*5*3 or 75 tiles.
* - While these numbers are low, on a huge level it is critical.
* - Say on a 512*512*3 or 786432 tiles, tiles to render is only 75.
* - Yet it is about 0.0001% of the level we render ...
* - We can thus keep a 60 fps frame rate with this method. */
Bounds.Clear();
var layers = (int) Size.Z;
for (int layer = 0; layer < layers; layer++)
{
/* We divide here to make far layers moving more slowly
* than layers that are closer to us OR Parallax Scrolling ! */
Vector2 topLeft = TilesFromPixels(Camera);
topLeft /= (layer + 1);
/* We draw what's visible on the screen. */
Vector2 bottomRight = topLeft + new Vector2(w, h);
bottomRight = Vector2.Clamp(bottomRight, Vector2.Zero, max1);
// Now we have bounds of regions to render for all layers !
Bounds.Add(new Bounds(topLeft, bottomRight));
}
base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
_spriteBatch.Begin();
if (Tiles != null)
{
/* We're going to draw level from back to front
* BUT the most important and a huge CPU saver is that
* we're going to draw only what we can see on screen,
* thus, we can have gigantic levels and they'll
* still at run 60 fps. */
_tilesRendered = 0;
var layers = (int) (Size.Z - 1);
for (int z = layers; z >= 0; z--)
{
Bounds bounds = Bounds[z];
Vector2 topLeft = bounds.TopLeft;
Vector2 bottomRight = bounds.BottomRight;
Vector2 translation = Camera/(z + 1); // Scrolling difference
for (var x = (int) topLeft.X;
x < bottomRight.X;
x++)
{
for (var y = (int) topLeft.Y;
y < bottomRight.Y;
y++)
{
Tile tile = Tiles[x, y, z];
if (tile == null) continue;
Vector2 position = PixelsFromTiles(new Vector2(x, y));
_spriteBatch.Draw(this.GetTexture(tile.Texture), position - translation, tile.Color);
_tilesRendered++;
}
}
}
}
_spriteBatch.End();
base.Draw(gameTime);
}
}
public static class Helpers
{
/// <summary>
/// Get pixel position from a tile position, according a tile size.
/// </summary>
/// <param name="x">Position horizontal value, as tiles.</param>
/// <param name="y">Position vertical value, as tiles.</param>
/// <param name="tileWidth">Tile width, as pixels.</param>
/// <param name="tileHeight">Tile height, as pixels.</param>
/// <returns>Position, as pixels.</returns>
public static Vector2 PixelsFromTiles(float x, float y, float tileWidth, float tileHeight)
{
return new Vector2(x * tileWidth, y * tileHeight);
}
/// <summary>
/// Get pixel position from a tile position, according a tile size.
/// </summary>
/// <param name="position">Position, as tiles.</param>
/// <param name="tileSize">Tile size, as pixels.</param>
/// <returns>Position, as pixels.</returns>
public static Vector2 PixelsFromTiles(Vector2 position, Vector2 tileSize)
{
return PixelsFromTiles(position.X, position.Y, tileSize.X, tileSize.Y);
}
/// <summary>
/// Get tile position from a pixel position, according a tile size.
/// </summary>
/// <param name="x">Position horizontal value, as pixels.</param>
/// <param name="y">Position vertical value, as pixels.</param>
/// <param name="tileWidth">Tile width, as pixels.</param>
/// <param name="tileHeight">Tile height, as pixels.</param>
/// <returns>Position, as tiles.</returns>
public static Vector2 TilesFromPixels(float x, float y, float tileWidth, float tileHeight)
{
return new Vector2(x / tileWidth, y / tileHeight);
}
/// <summary>
/// Get tile position from a pixel position, according a tile size.
/// </summary>
/// <param name="position">Position, as pixels.</param>
/// <param name="tileSize">Tile size, as pixels.</param>
/// <returns>Position, as tiles.</returns>
public static Vector2 TilesFromPixels(Vector2 position, Vector2 tileSize)
{
return TilesFromPixels(position.X, position.Y, tileSize.X, tileSize.Y);
}
}
}