From cb604355b27dba37a7bfcc8f095fc0a300be24af Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Sat, 7 Sep 2024 17:46:18 +0100
Subject: [PATCH 1/6] Initial release of Tile Engine

---
 video/vdu_layers.h           | 1148 ++++++++++++++++++++++++++++++++++
 video/vdu_stream_processor.h |   65 ++
 video/vdu_sys.h              |    4 +
 3 files changed, 1217 insertions(+)
 create mode 100644 video/vdu_layers.h

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
new file mode 100644
index 00000000..28a42097
--- /dev/null
+++ b/video/vdu_layers.h
@@ -0,0 +1,1148 @@
+#ifndef AGON_LAYERS_H
+#define AGON_LAYERS_H
+
+// Title:			Agon Tile Engine
+// Author:			Julian Regel
+// Created:			07/09/2024
+// Last Updated:	07/09/2024
+
+// This code included in VDP by adding:
+// #include "vdu_layers.h"
+// in vdu_sys.h and is called by VDUStreamProcessor::vdu_sys_video()
+
+#define VDP_LAYERS							0xC2		// VDU 23,0,194
+#define VDP_LAYER_TILEBANK_INIT				0x00		// VDU 23,0,194,0
+#define VDP_LAYER_TILEBANK_LOAD				0x01		// VDU 23,0,194,1
+#define VDP_LAYER_TILEBANK_LOAD_BUFFER		0x02		// VDU 23,0,194,2	[Future]
+#define VDP_LAYER_TILEBANK_DRAW				0x06		// VDU 23,0,194,6
+#define VDP_LAYER_TILEBANK_FREE				0x07		// VDU 23,0,194,7
+#define VDP_LAYER_TILEPALETTE_INIT			0x08		// VDU 23,0,194,8	[Future]
+#define VDP_LAYER_TILEPALETTE_SET			0x09		// VDU 23,0,194,9	[Future]
+#define VDP_LAYER_TILEPALETTE_SET_MULTIPLE	0x0A		// VDU 23,0,194,10	[Future]
+#define VDP_LAYER_TILEPALETTE_ACTIVATE		0x0E		// VDU 23,0,194,14 	[Future]
+#define VDP_LAYER_TILEPALETTE_FREE			0x0F		// VDU 23,0,194,15 	[Future]
+#define VDP_LAYER_TILEMAP_INIT				0x10		// VDU 23,0,194,16
+#define VDP_LAYER_TILEMAP_SET_TILE			0x11		// VDU 23,0,194,17
+#define VDP_LAYER_TILEMAP_SET_MULTIPLE		0x12		// VDU 23,0,194,18	[Future]
+#define VDP_LAYER_TILEMAP_FREE				0x17		// VDU 23,0,194,23
+#define VDP_LAYER_TILELAYER_INIT			0x18		// VDU 23,0,194,24
+#define VDP_LAYER_TILELAYER_SET_PROPERTY	0x19		// VDU 23,0,194,25
+#define VDP_LAYER_TILELAYER_SET_SCROLL		0x1A		// VDU 23,0,194,26
+#define VDP_LAYER_TILELAYER_SET_TABLE		0x1B		// VDU 23,0,194,27	[Future]
+#define VDP_LAYER_TILELAYER_SET_DRAW_PARAM	0x1D		// VDU 23,0,194,29 	[Future]
+#define VDP_LAYER_TILELAYER_DRAW			0x1E		// VDU 23,0,194,30
+#define VDP_LAYER_TILELAYER_FREE			0x1F		// VDU 23,0,194,31
+
+
+void VDUStreamProcessor::vdu_sys_layers(void) {
+
+	auto cmd = readByte_t();
+
+	switch (cmd) {
+
+		// Tile Bank Functions
+
+		case VDP_LAYER_TILEBANK_INIT: {
+
+			// VDU 23,0,194,0,<tileBankNum>,<tileBankBitDepth>,<reservedParameter1>,<reservedParameter2>
+
+			uint8_t tileBankNum = readByte_t();			// 0 [Future: 0-3]
+			uint8_t tileBankBitDepth = readByte_t();	// 0 = 64 colours, [Future: 1 = 16 colours, 2 = 4 colours, 3 = 2 colours]
+			uint8_t reservedParameter1 = readByte_t();	// Ignored in release 1.0. Should be set to 0.
+			uint8_t reservedParameter2 = readByte_t();	// Ignored in release 1.0. Should be set to 0.
+			
+			vdu_sys_layers_tilebank_init(tileBankNum, tileBankBitDepth);
+
+		} break;
+
+		case VDP_LAYER_TILEBANK_LOAD: {
+
+			// VDU 23,0,194,1,<tileBankNum>,<tileNumber>,<pixel0>,...<pixel63>
+
+			uint8_t tileBankNum = readByte_t();			// 0 [Future: 0-3]
+			uint8_t tileId = readByte_t();				// 0-255
+
+			vdu_sys_layers_tilebank_load(tileBankNum, tileId);
+
+		} break;
+
+		case VDP_LAYER_TILEBANK_LOAD_BUFFER: {
+
+		} break;
+
+		case VDP_LAYER_TILEBANK_DRAW: {
+
+			// VDU 23,0,194,6,<tileBankNum>,<tileId>,<palette>,<xpos>,<ypos>,<xoffset>,<yoffset>,<attribute>
+
+			uint8_t tileBankNum = readByte_t();
+			uint8_t tileId = readByte_t();
+			uint8_t palette = readByte_t();
+			uint8_t xPos = readByte_t();
+			uint8_t yPos = readByte_t();
+			uint8_t xOffset = readByte_t();
+			uint8_t yOffset = readByte_t();
+			uint8_t tileAttribute = readByte_t();
+
+			vdu_sys_layers_tilebank_draw(tileBankNum, tileId, palette, xPos, yPos, xOffset, yOffset, tileAttribute);
+
+		} break;
+
+		case VDP_LAYER_TILEBANK_FREE: {
+
+			uint8_t tileBankNum = readByte_t();
+
+			vdu_sys_layers_tilebank_free(tileBankNum);
+
+		} break;
+
+		// Tile Map Functions
+
+		case VDP_LAYER_TILEMAP_INIT: {
+
+			// VDU 23,0,194,16,<tilelayernumber>,<tilemapsize>,<reservedParameter0>,<reservedParameter1>
+			// Size is one of:
+			//		0 = 32x32, 1=32x64, 2=32x128, 3=64x32, 4=64x64, 5=64x128, 6=128x32, 7=128x64, 8=128x128
+
+			uint8_t tileLayerNum = readByte_t();
+			uint8_t tileMapSize = readByte_t();
+			uint8_t reservedParameter1 = readByte_t();	// Ignored in release 1.0. Should be set to 0.
+			uint8_t reservedParameter2 = readByte_t();	// Ignored in release 1.0. Should be set to 0.
+
+			vdu_sys_layers_tilemap_init(tileLayerNum, tileMapSize);
+
+
+		} break;
+
+		case VDP_LAYER_TILEMAP_SET_TILE: {
+
+			// VDU 23,0,194,17,<tilelayernumber>,<xpos>,<ypos>,<tileid>,<tileattribute>
+
+			uint8_t tileLayerNum = readByte_t();
+			uint8_t xPos = readByte_t();
+			uint8_t yPos = readByte_t();
+			uint8_t tileId = readByte_t();
+			uint8_t tileAttribute = readByte_t();
+
+			vdu_sys_layers_tilemap_set(tileLayerNum, xPos, yPos, tileId, tileAttribute);
+
+		} break;		
+
+		case VDP_LAYER_TILEMAP_SET_MULTIPLE: {
+
+		} break;
+
+		case VDP_LAYER_TILEMAP_FREE: {
+
+			// VDU 23,0,194,23,<tileMapNum>
+
+			uint8_t tileMapNum = readByte_t();
+
+			vdu_sys_layers_tilemap_free(tileMapNum);
+
+		} break;
+
+		// Tile Layer Functions
+
+		case VDP_LAYER_TILELAYER_INIT: {
+	
+			// VDU 23,0,194,24,<tileLayerNum>,<tileLayerSize>,<tileSize>,<reservedParameter1>
+			//
+			// tileLayerSize is one of:
+			// 0 = 80x60 (640x480 modes), 1 = 80x30 (640x240 modes), 2 = 40x30 (320x240 modes), 3 = 40x25 (320x200 modes)
+
+			uint8_t tileLayerNum = readByte_t();
+			uint8_t tileLayerSize = readByte_t();
+			uint8_t tileSize = readByte_t();
+			uint8_t reservedParameter1 = readByte_t();	// Ignored in release 1.0. Should be set to 0.
+		
+
+			vdu_sys_layers_tilelayer_init(tileLayerNum, tileLayerSize, tileSize);
+	
+		} break;
+
+		case VDP_LAYER_TILELAYER_SET_PROPERTY: {
+
+		} break;
+
+		case VDP_LAYER_TILELAYER_SET_SCROLL: {
+
+			// VDU 23,0,194,26,<tileLayerNum>,<xpos>,<ypos>,<xoffset>,<yoffset>
+
+			uint8_t tileLayerNum = readByte_t();
+			uint8_t xPos = readByte_t();
+			uint8_t yPos = readByte_t();
+			uint8_t xOffset = readByte_t();
+			uint8_t yOffset = readByte_t();
+
+			vdu_sys_layers_tilelayer_set_scroll(tileLayerNum, xPos, yPos, xOffset, yOffset);
+
+		} break;
+
+		case VDP_LAYER_TILELAYER_SET_TABLE: {
+
+		} break;
+
+		case VDP_LAYER_TILELAYER_DRAW: {
+
+			// VDU 23,0,194,30,<tileLayerNum>
+			// Parameters are currently undefined but would facilitate drawing priority
+
+			uint8_t tileLayerNum = readByte_t();
+
+			vdu_sys_layers_tilelayer_draw(tileLayerNum);
+
+		} break;
+
+		case VDP_LAYER_TILELAYER_FREE: {
+			// Not required? We don't dynamically allocate memory in VDP_LAYER_TILELAYER_INIT
+			// so there is nothing to free?
+		} break;
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilebank_init(uint8_t tileBankNum, uint8_t tileBankBitDepth) {
+
+	// Initial release to only support 8bpp tiles
+	if (tileBankBitDepth != 0) return;
+
+	// Initial release to only support 8x8 tiles
+	uint8_t tileBankTileWidth = 8;
+	uint8_t tileBankTileHeight = 8;
+
+	// Create a buffer of width, height and colour depth for 256 tiles
+	int tileBankBufferSize = tileBankTileWidth * tileBankTileHeight * 256;
+
+	switch (tileBankNum) {		// Which tile bank is being initiated?
+
+		case 0: {				// Tile Bank 0
+			
+			// Check if already exists
+			
+			if (tileBank0Data !=NULL) {
+
+				// If already exists, then free and reallocate
+				vdu_sys_layers_tilebank_free(tileBankNum);
+			}
+			
+			// Now allocate the new memory
+			tileBank0Data = heap_caps_malloc(tileBankBufferSize,MALLOC_CAP_SPIRAM);
+						
+			// Only continue if the reinit was successful
+			
+			if (tileBank0Data != NULL) {
+
+				// Cast the void pointer to an integer
+				tileBank0Ptr = (uint8_t *)tileBank0Data;
+
+				// Set every byte in the tile bank to 0
+				for (auto i=0; i <tileBankBufferSize; i++)
+					*(tileBank0Ptr + i) = 0; 
+			}
+			else {
+				discardBytes(tileBankBufferSize);
+			}
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilebank_init: Invalid tilebank %d specified.\r\n",tileBankNum);
+			return;
+		}
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilebank_load(uint8_t tileBankNum, uint8_t tileId) {
+
+	// Note: In the initial release of the Tile Engine, the tileBankNum is ignored as there is only
+	// a single tile bank.
+
+	uint8_t sourcePixel = 0;
+	int destPixel = 0;
+
+	// Only do something if the tilebank exists
+
+	if (tileBank0Data != NULL) {
+	
+		// Initial implementation is hardcoded to 8x8 tiles and 64 colours (i.e., 64 pixels * 1 byte per pixel)
+
+		for (auto n=0; n<64; n++) {
+			sourcePixel = readByte_t();
+			destPixel = (tileId * 64) + n;
+			tileBank0Ptr[destPixel]= sourcePixel;
+		}
+	} else {
+		debug_log("vdu_sys_layers_tilebank_load: tileBank0Data is not defined.\r\n");
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilebank_draw(uint8_t tileBankNum, uint8_t tileId, uint8_t palette, uint8_t xPos, uint8_t yPos, uint8_t xOffset, uint8_t yOffset, uint8_t tileAttribute) {
+
+	// Initial release only supports tileBank 0
+
+	if (tileBankNum != 0) {
+		debug_log("vdu_sys_layers_tilebank_draw: Invalid tileBankNum %d specified.\r\n",tileBankNum);
+		return;
+	}
+	
+	// tileId 0 is special so cannot be drawn
+	if (tileId == 0) return;
+
+	int xPix, yPix;
+
+	if (tileBank0Data != NULL) {
+
+		// Check attribute bits 0 and 1 to get tile draw direction and call appropriate draw function
+
+		uint8_t tileFlip = tileAttribute & 0x03;
+
+		switch (tileFlip) {
+			case 0x00:		// Normal drawing
+				writeTileToBuffer(tileId, 0, 0, currentTileDataBuffer, 1);
+				break;
+			case 0x01:		// Flip X
+				writeTileToBufferFlipX(tileId, 0, 0, currentTileDataBuffer, 1);
+				break;
+			case 0x02:		// Flip Y
+				writeTileToBufferFlipY(tileId, 0, 0, currentTileDataBuffer, 1);
+				break;
+			case 0x03:		// Flip X and Y
+				writeTileToBufferFlipXY(tileId, 0, 0, currentTileDataBuffer, 1);
+				break;
+		}
+
+		xPix = (xPos * 8) - xOffset;
+		yPix = (yPos * 8) - yOffset;
+
+		currentTile = Bitmap(8, 8, currentTileDataBuffer, PixelFormat::RGBA2222);
+
+		// Draw Tile
+
+		canvas->drawBitmap(xPix,yPix,&currentTile);
+
+		waitPlotCompletion();
+
+	} else {
+		debug_log("vdu_sys_layers_tilebank_draw: tileBank0Data not initialised.\r\n");
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilebank_free(uint8_t tileBankNum) {
+
+	switch (tileBankNum) {
+		case 0: {
+			if (tileBank0Data != NULL) {
+				debug_log("vdu_sys_layers_tilebank_free: Freeing tileBank0Data.\r\n");
+				heap_caps_free(tileBank0Data);
+				tileBank0Data = NULL;
+			}
+		} break;
+		
+		default: {
+			debug_log("vdu_sys_layers_tilebank_free: Invalid tileBankNum %d specified.\r\n",tileBankNum);
+		}
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8_t tileMapSize) {
+
+	// Check if tileMap0 already exists and free if it is.
+
+	switch (tileLayerNum) {
+
+		case 0: {		// If tilemap/layer number is 0
+			
+			// Check if already exists
+	
+			if (tileMap0 !=NULL) {
+				// If already exists, then free and reinitialise
+
+				vdu_sys_layers_tilemap_free(tileLayerNum); 
+			}
+			
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilemap_init: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+			return;
+		}
+	}
+
+	//	The following tile map sizes are supported:
+	//	0=32x32, 1=32x64, 2=32x128, 3=64x32, 4=64x64, 5=64x128, 6=128x32, 7=128x64, 8=128x128
+
+	switch (tileMapSize) {
+
+		case 0: {		// 32x32 tilemap
+			
+			tileMap0Properties.width = 32;
+			tileMap0Properties.height = 32;
+
+		} break;
+
+		case 1: {		// 32x64 tilemap
+			
+			tileMap0Properties.width = 32;
+			tileMap0Properties.height = 64;
+
+		} break;
+
+		case 2: {		// 32x128 tilemap
+			
+			tileMap0Properties.width = 32;
+			tileMap0Properties.height = 128;
+
+		} break;
+
+		case 3: {		// 64x32 tilemap
+			
+			tileMap0Properties.width = 64;
+			tileMap0Properties.height = 32;
+
+		} break;
+
+		case 4: {		// 64x64 tilemap
+			
+			tileMap0Properties.width = 64;
+			tileMap0Properties.height = 64;
+
+		} break;
+
+		case 5: {		// 64x128 tilemap
+			
+			tileMap0Properties.width = 64;
+			tileMap0Properties.height = 128;
+
+		} break;
+
+		case 6: {		// 128x32 tilemap
+			
+			tileMap0Properties.width = 128;
+			tileMap0Properties.height = 32;
+
+		} break;
+
+		case 7: {		// 128x64 tilemap
+			
+			tileMap0Properties.width = 128;
+			tileMap0Properties.height = 64;
+
+		} break;
+
+		case 8: {		// 128x128 tilemap
+			
+			tileMap0Properties.width = 128;
+			tileMap0Properties.height = 128;
+
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilemap_init: Invalid tileMapSize %d specified.\r\n",tileMapSize);
+			return;
+		}
+	}
+
+	uint8_t tileMapWidth = tileMap0Properties.width;
+	uint8_t tileMapHeight = tileMap0Properties.height;
+
+	int tileMapBufferSize = tileMapWidth * sizeof(struct Tile*);
+
+	// Initial release to only support a single tile layer
+	if (tileLayerNum != 0) {
+		debug_log("vdu_sys_layers_tilemap_init: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+		return;
+	}
+
+	switch (tileLayerNum) {
+
+		case 0: {		// If tile map number is 0
+						
+			// Allocate memory for each column in the tile map
+			tileMap0 = (struct Tile**)heap_caps_malloc(tileMapBufferSize,MALLOC_CAP_SPIRAM);
+
+			// Allocate memory for each row in the tile map
+			for (auto i=0; i<tileMapWidth; i++) {
+				tileMap0[i] = (struct Tile*)heap_caps_malloc(tileMapHeight * sizeof(struct Tile),MALLOC_CAP_SPIRAM);
+			}
+						
+			// Only continue if the init was successful
+			
+			if (tileMap0 != NULL) {
+
+				// Set every byte in the tile map to 0
+
+				for (auto i=0; i<tileMapWidth; i++) {
+					for (auto j=0; j<tileMapHeight; j++) {
+						tileMap0[i][j].id = 0;
+						tileMap0[i][j].attribute = 0 ;
+					}
+				}
+			}
+			else {
+				debug_log("vdu_sys_layers_tilemap_init: Error when attempting to allocate memory for tilemap.\r\n");
+				discardBytes(tileMapBufferSize);
+			}
+		} break;
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilemap_set(uint8_t tileLayerNum, uint8_t xPos, uint8_t yPos, uint8_t tileId, uint8_t tileAttribute) {
+	
+	switch (tileLayerNum) {
+		case 0: {
+			if (tileMap0 != NULL) {
+				// Skip if passed x and y are greater than the size of the tilemap
+				if (xPos >= tileMap0Properties.width || yPos >= tileMap0Properties.height) return;
+
+				if (tileMap0 != NULL) {
+					tileMap0[xPos][yPos].id = tileId;
+					tileMap0[xPos][yPos].attribute = tileAttribute;
+				}
+			}
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilemap_set: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+			return;
+		}
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilemap_free(uint8_t tileLayerNum) {
+
+	switch (tileLayerNum) {
+		case 0: {
+
+			uint8_t tileMapWidth = tileMap0Properties.width;
+
+			if (tileMap0 != NULL) {
+
+				debug_log("vdu_sys_layers_tilemap_free: Freeing tileMap0.\r\n");
+
+				for (auto i=0; i<tileMapWidth; i++) {
+					heap_caps_free(tileMap0[i]);
+				}
+				heap_caps_free(tileMap0);
+
+				tileMap0 = NULL;
+			}
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilemap_free: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+			return;
+		}
+	}		
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uint8_t tileLayerSize, uint8_t tileSize) {
+	
+	uint8_t tileLayerHeight;
+	uint8_t tileLayerWidth;
+
+	switch (tileLayerSize) {
+
+		case 0: {		// 80x60 layer
+			
+			tileLayerHeight = 60;
+			tileLayerWidth = 80;
+
+		} break;
+
+		case 1: {		// 80x30 layer
+			
+			tileLayerHeight = 30;
+			tileLayerWidth = 80;
+
+		} break;
+
+		case 2: {		// 40x30 layer
+			
+			tileLayerHeight = 30;
+			tileLayerWidth = 40;
+
+		} break;
+
+		case 3: {		// 40x25 layer
+			
+			tileLayerHeight = 25;
+			tileLayerWidth = 40;
+
+		} break;
+		
+		default: {
+			debug_log("vdu_sys_layers_tilelayer_init: Invalid tileLayerSize %d specified.\r\n",tileLayerSize);
+			return;
+		}
+	}
+
+	int rowBufferWidth = tileLayerWidth * 8;						// Number of bytes (pixels) wide
+
+	int rowBufferHeight = 8;										// Number of scanlines in the buffer
+
+	int rowDataBufferSize = rowBufferWidth * rowBufferHeight;		// Size of the buffer in bytes
+
+	// Create the row bitmap
+
+	currentRow = Bitmap(rowBufferWidth, rowBufferHeight, currentRowDataBuffer, PixelFormat::RGBA2222);
+
+	// Zero out the active parts of the buffer
+
+	for (auto i=0; i<rowDataBufferSize; i++) {
+		currentRowDataBuffer[i] = 0;
+	}
+
+	switch (tileLayerNum) {
+
+		case 0: {		// Tile Layer 0
+			
+			tileLayer0.height = tileLayerHeight;
+			tileLayer0.width = tileLayerWidth;
+			tileLayer0.sourceXPos = 0;
+			tileLayer0.sourceYPos = 0;
+			tileLayer0.xOffset = 0;
+			tileLayer0.yOffset = 0;
+			tileLayer0.attribute = 0;
+
+			tileLayer0init = 1;		// Set as initialised
+
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilelayer_init: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+			return;
+		}
+
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNum, uint8_t xPos, uint8_t yPos, uint8_t xOffset, uint8_t yOffset) {
+
+	switch (tileLayerNum) {
+		
+		case 0: {
+
+			if (tileLayer0init != 0) {		// Only continue if the tile layer is initialised
+				if (tileMap0 != NULL) {		// Only continue if the tile map is initialised
+
+					uint8_t tileMapWidth = tileMap0Properties.width;
+					uint8_t tileMapHeight = tileMap0Properties.height;
+
+					if (xPos >= tileMapWidth) { xPos = 0; }
+					if (yPos >= tileMapHeight) { yPos = 0; }
+
+					if (xOffset > 7) { xOffset = 0; }
+					if (yOffset > 7) { yOffset = 0;	}
+
+					tileLayer0.sourceXPos = xPos;
+					tileLayer0.sourceYPos = yPos;
+					tileLayer0.xOffset = xOffset;
+					tileLayer0.yOffset = yOffset;
+				} else {
+					debug_log("vdu_sys_layers_tilelayer_set_scroll: tileMap0 is not initialised.\r\n");
+					return;
+				}
+			} else {
+				debug_log("vdu_sys_layers_tilelayer_set_scroll: tileLayer is not initialised.\r\n");
+				return;
+			}
+
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilelayer_set_scroll: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+			return;
+		}
+	}
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
+
+	// Timing debug statements
+
+	// auto startTime = xTaskGetTickCountFromISR();
+  	// auto endTime = xTaskGetTickCountFromISR();
+	// auto sumTime = xTaskGetTickCountFromISR();
+	// sumTime = 0;
+
+	int xPix = 0;		// X position in pixels is now always 0 as the offset is written directly to the tileRowBuffer
+	int yPix = 0;
+
+	uint8_t tileId;
+	uint8_t tileAttribute;
+	uint8_t tileLayerHeight;
+	uint8_t tileLayerWidth;
+	uint8_t sourceXPos;
+	uint8_t sourceYPos;
+	uint8_t xOffset;
+	uint8_t yOffset;
+	uint8_t tileMapWidth;
+	uint8_t tileMapHeight;
+	uint8_t tileRowOffset = 0;
+
+	switch (tileLayerNum) {
+
+		case 0: {
+
+			if (tileLayer0init != 0) {
+				if (tileMap0 != NULL) {
+
+					tileLayerHeight = tileLayer0.height;
+					tileLayerWidth = tileLayer0.width;
+
+					sourceXPos = tileLayer0.sourceXPos;
+					sourceYPos = tileLayer0.sourceYPos;
+
+					xOffset = tileLayer0.xOffset;
+					yOffset = tileLayer0.yOffset;
+
+					tileMapWidth = tileMap0Properties.width;
+					tileMapHeight = tileMap0Properties.height;
+				} else {
+					debug_log("vdu_sys_layers_tilelayer_draw: tileMap0 is not initialised.\r\n");
+					return;
+				}
+			} else {
+				debug_log ("vdu_sys_layers_tilelayer_draw: tileLayer0 is not initialised.\r\n");
+				return;
+			}
+		} break;
+
+		default: {
+			debug_log("Invalid tileLayerNum: %d\r\n",tileLayerNum);
+			return;
+		}
+	}
+
+	int rowBufferWidth = tileLayerWidth * 8;
+	int rowBufferHeight = 8;
+	int rowDataBufferSize = rowBufferWidth * rowBufferHeight;
+
+	// Perform validation checks
+
+	if (sourceXPos >= tileMapWidth) { sourceXPos = 0; }
+	if (sourceYPos >= tileMapHeight) { sourceYPos = 0; }
+
+	// Do not continue if tileBank is not initialised.
+	if (tileBank0Data == NULL) { 
+		debug_log("vdu_sys_layers_tilelayer_draw: tileBank0Data is not initialised.\r\n");
+		return;
+	}
+
+	// Process rows for each frame
+
+	for (auto y=0; y<=tileLayerHeight; y++) {				
+
+		// Clear Row Buffer
+
+		for (auto i=0; i<rowDataBufferSize; i++) {
+			currentRowDataBuffer[i] = 0;
+		}
+
+		// Process tile map for current row
+
+		for (auto x=0; x<=tileLayerWidth; x++) {
+																
+			// read the Tile Map
+
+			tileId = tileMap0[sourceXPos][sourceYPos].id;
+
+			tileAttribute = tileMap0[sourceXPos][sourceYPos].attribute;
+
+			tileRowOffset = x;	
+
+			if (tileId == 0) {		// Tile 0 is special...
+
+				// What you do here depends on the tile attribute in "special" mode:
+
+				switch (tileAttribute) {
+
+					case 0: {		// Tile is transparent and not drawn
+
+					} break;
+				}
+			} else {				// Tile is normal and should be processed accordingly
+
+				// Check attribute to get tile draw direction and call appropriate draw function
+
+				uint8_t tileFlip = tileAttribute & 0x03;
+
+				switch (tileFlip) {
+					case 0x00:		// Normal drawing
+						writeTileToBuffer(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						break;
+					case 0x01:		// Flip X
+						writeTileToBufferFlipX(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						break;
+					case 0x02:		// Flip Y
+						writeTileToBufferFlipY(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						break;
+					case 0x03:		// Flip X and Y
+						writeTileToBufferFlipXY(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						break;
+					default:
+						debug_log("Invalid tileAttribute value: %d\r\n",tileFlip);
+				}
+			}
+
+			// If we're at the edge of the tile map, reset to the beginning.
+
+			sourceXPos++;
+			if (sourceXPos == tileMapWidth) { 
+				sourceXPos = 0; 
+			}
+		}
+
+		// At the end of the row, reset sourceXPos back (else it will keep incrementing)
+		sourceXPos = tileLayer0.sourceXPos;
+		
+		// Set Y position to display on screen
+		yPix = (y * 8) - tileLayer0.yOffset;		// Y position in pixels 
+	
+		// Draw Row
+
+		// startTime = xTaskGetTickCountFromISR();
+
+		currentRow = Bitmap(rowBufferWidth, rowBufferHeight, currentRowDataBuffer, PixelFormat::RGBA2222);
+
+		canvas->drawBitmap(xPix,yPix,&currentRow);		
+
+		waitPlotCompletion();
+
+		// endTime = xTaskGetTickCountFromISR();
+		// auto elapsedTime = endTime - startTime;
+		// sumTime = sumTime + elapsedTime;
+
+		sourceYPos++;
+		if (sourceYPos == tileMapHeight) {
+			sourceYPos = 0; 
+		}
+	}
+
+	// debug_log("vdu_sys_layers_tilelayer_draw: Sum time is %d\r\n",sumTime);
+}
+
+// Tile drawing functions
+
+void VDUStreamProcessor::writeTileToBuffer(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth) {
+
+	int destStartPos;
+	int destRowStart;
+	int destPixel;
+	int bufferWidth = tileLayerWidth * 8;
+
+	// Read 64 bytes from the data bank and write to the tile buffer
+
+	int sourcePixel = tileId * 64;
+
+	// Handle the first column on screen which may only be partially drawn depending on the xOffset value
+	// This is also called by vdu_sys_layers_tilebank_draw() when drawing a single tile.
+
+	if (tileCount == 0) {
+
+		destStartPos = 0;
+
+		sourcePixel = sourcePixel + xOffset;				// Need to start reading the source data at the xOffset
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			for (auto x=xOffset; x<8; x++) {				// Start counting at xOffset through to end of the tile line
+
+				destPixel = destRowStart + x - xOffset;
+
+				tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+
+				sourcePixel++;
+			}
+
+			sourcePixel = sourcePixel + xOffset;
+		}
+	}
+
+	// General drawing code for all columns except the first and last columns
+
+	if (tileCount > 0 && tileCount < tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			for (auto x=0; x<8; x++) {
+
+				destPixel = destRowStart + x;
+
+				tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+
+				sourcePixel++;
+			}
+		}
+	}
+
+	// Handle the final column on screen which may only be partially drawn depending on the xOffset value
+
+	if (tileCount == tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			for (auto x=0; x<xOffset; x++) {				// Start counting at the start of the tile through to the xOffset
+
+				destPixel = destRowStart + x;
+
+				tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+
+				sourcePixel++;
+			}
+
+			sourcePixel = sourcePixel + (8 - xOffset);		// As we are not drawing all pixels in the tile, skip ahead to the next line
+		}
+	}
+}
+
+void VDUStreamProcessor::writeTileToBufferFlipX(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth) {
+
+	int destStartPos;
+	int destRowStart;
+	int destPixel;
+	int bufferWidth = tileLayerWidth * 8;
+
+	int sourceTile = tileId * 64;		// The start of the specified tile Id
+	int sourcePixel = 0;
+
+	// Handle the first column on screen which may only be partially drawn depending on the xOffset value.
+	// This is also called by vdu_sys_layers_tilebank_draw() when drawing a single tile.
+
+	if (tileCount == 0) {
+
+		destStartPos = 0;
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			destPixel = destRowStart;
+
+			for (auto x=(7-xOffset); x>=0; x--) {		
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destPixel = destPixel + xOffset;
+		}
+	}
+
+	// General drawing code for all columns except the first and last columns
+
+	if (tileCount > 0 && tileCount < tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			destPixel = destRowStart;
+
+			for (auto x=7; x>=0; x--) {		
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+		}
+	}
+
+	// Handle the final column on screen which may only be partially drawn depending on the xOffset value
+
+	if (tileCount == tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=0; y<8; y++) {
+
+			destRowStart = destStartPos + (bufferWidth * y);
+
+			destPixel = destRowStart;
+
+			for (auto x=7; x>(7-xOffset); x--) {		
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destPixel = destPixel + xOffset;
+		}	
+	}		
+}
+
+void VDUStreamProcessor::writeTileToBufferFlipY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth) {
+
+	int destStartPos;		// Position in buffer the current tile needs to be written
+	int destRowStart;
+	int destPixel;
+	int bufferWidth = tileLayerWidth * 8;
+	
+	int sourceTile = tileId * 64;		// The start of the specified tile Id
+	int sourcePixel = 0;
+	int destRowCount = 0;
+
+	// Handle the first column on screen which may only be partially drawn depending on the xOffset value.
+	// This is also called by vdu_sys_layers_tilebank_draw() when drawing a single tile.
+
+	if (tileCount == 0) {
+
+		destStartPos = 0;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=xOffset; x<8; x++) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+			destPixel = destPixel + xOffset;
+		}
+
+	}
+
+	// General drawing code for all columns except the first and last columns
+
+	if (tileCount > 0 && tileCount < tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=0; x<8; x++) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+		}	
+
+	}
+
+	// Handle the final column on screen which may only be partially drawn depending on the xOffset value
+
+	if (tileCount == tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=0; x<xOffset; x++) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+			destPixel = destPixel + xOffset;
+		}	
+	}
+}
+
+void VDUStreamProcessor::writeTileToBufferFlipXY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth) {
+
+	int destStartPos; 		// Position in buffer the current tile needs to be written
+	int destRowStart;
+	int destPixel;
+	int bufferWidth = tileLayerWidth * 8;
+	
+	int sourceTile = tileId * 64;		// The start of the specified tile Id
+	int sourcePixel = 0;
+	int destRowCount = 0;
+
+	// Handle the first column on screen which may only be partially drawn depending on the xOffset value.
+	// This is also called by vdu_sys_layers_tilebank_draw() when drawing a single tile.
+
+	if (tileCount == 0) {
+
+		destStartPos = 0;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=(7-xOffset); x>=0; x--) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+			destPixel = destPixel + xOffset;
+		}	
+	}
+
+	// General drawing code for all columns except the first and last columns
+
+	if (tileCount > 0 && tileCount < tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=7; x>=0; x--) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+		}	
+	}
+
+	// Handle the final column on screen which may only be partially drawn depending on the xOffset value
+
+	if (tileCount == tileLayerWidth) {
+
+		destStartPos = (tileCount * 8) - xOffset;
+
+		for (auto y=7; y>=0; y--) {
+
+			destRowStart = destStartPos + (bufferWidth * destRowCount);
+
+			destPixel = destRowStart;
+
+			for (auto x=7; x>(7-xOffset); x--) {
+				sourcePixel = sourceTile + (y * 8) + x;
+				tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+				destPixel++;
+			}
+
+			destRowCount++;
+			destPixel = destPixel + xOffset;
+		}
+
+	}
+}
+
+#endif // AGON_LAYERS_H
\ No newline at end of file
diff --git a/video/vdu_stream_processor.h b/video/vdu_stream_processor.h
index a1e7d870..d26bf2ec 100644
--- a/video/vdu_stream_processor.h
+++ b/video/vdu_stream_processor.h
@@ -140,6 +140,71 @@ class VDUStreamProcessor {
 		void receiveFirmware();
 		void switchFirmware();
 
+				// Begin: Tile Engine
+
+		void vdu_sys_layers();
+		void vdu_sys_layers_tilebank_init(uint8_t tileBankNum, uint8_t tileBankBitDepth);
+		void vdu_sys_layers_tilebank_load(uint8_t tileBankNum, uint8_t tileId);
+		void vdu_sys_layers_tilebank_draw(uint8_t tileBankNum, uint8_t tileId, uint8_t palette, uint8_t x, uint8_t y, uint8_t xOffset, uint8_t yOffset, uint8_t tileAttribute);
+		void vdu_sys_layers_tilebank_free(uint8_t tileBankNum);
+		void vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8_t tileMapSize);
+		void vdu_sys_layers_tilemap_set(uint8_t tileLayerNum, uint8_t x, uint8_t y, uint8_t tileId, uint8_t tileAttribute);
+		void vdu_sys_layers_tilemap_free(uint8_t tileMapNum);
+		void vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uint8_t tileLayerSize, uint8_t tileSize);
+		void vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNum, uint8_t x, uint8_t y, uint8_t xOffset, uint8_t yOffset);
+		void vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum);
+		void writeTileToBuffer(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
+		void writeTileToBufferFlipX(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
+		void writeTileToBufferFlipY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
+		void writeTileToBufferFlipXY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
+
+		// Tile Bank variables
+
+		void * tileBank0Data = NULL;
+		uint8_t * tileBank0Ptr;
+
+		Bitmap currentTile; 
+		uint8_t currentTileDataBuffer[64];
+
+		// Tile Map variables
+
+		struct Tile {
+			uint8_t	id;
+			uint8_t attribute;
+		};
+
+		struct Tile** tileMap0 = NULL;
+
+		struct TileMap {
+			uint8_t height;
+			uint8_t width;
+		};
+
+		TileMap tileMap0Properties;
+
+		// Tile Layer variables
+
+		Bitmap currentRow;
+		uint8_t currentRowDataBuffer[5184];		// Buffer big enough for 64 byte tiles * 81 columns (the largest supported size +1)
+
+		struct TileLayer {
+			uint8_t height;
+			uint8_t width;
+			uint8_t sourceXPos;
+			uint8_t sourceYPos;
+			uint8_t xOffset;
+			uint8_t yOffset;
+			uint8_t attribute;
+		};
+
+		TileLayer tileLayer0;
+		TileLayer tileLayer1;
+		TileLayer tileLayer2;
+
+		uint8_t tileLayer0init = 0;		
+
+		// End: Tile Engine
+
 	public:
 		uint16_t id = 65535;
 
diff --git a/video/vdu_sys.h b/video/vdu_sys.h
index d755b45f..881227a6 100644
--- a/video/vdu_sys.h
+++ b/video/vdu_sys.h
@@ -18,6 +18,7 @@
 #include "vdu_sprites.h"
 #include "updater.h"
 #include "vdu_stream_processor.h"
+#include "vdu_layers.h"
 
 extern void startTerminal();					// Start the terminal
 extern void setConsoleMode(bool mode);			// Set console mode
@@ -308,6 +309,9 @@ void VDUStreamProcessor::vdu_sys_video() {
 				setLegacyModes((bool) b);
 			}
 		}	break;
+		case VDP_LAYERS: {				// VDU 23, 0, &C2, n
+			vdu_sys_layers();
+		}	break;
 		case VDP_SWITCHBUFFER: {		// VDU 23, 0, &C3
 			switchBuffer();
 		}	break;

From 7b37550b7c7c20ae473a5dc165510ff18815b6e9 Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Mon, 7 Oct 2024 18:34:19 +0100
Subject: [PATCH 2/6] Added debug_log_mem() and additional memory init/free
 checks

---
 video/vdu_layers.h | 108 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 90 insertions(+), 18 deletions(-)

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
index 28a42097..53c819fd 100644
--- a/video/vdu_layers.h
+++ b/video/vdu_layers.h
@@ -33,6 +33,9 @@
 #define VDP_LAYER_TILELAYER_DRAW			0x1E		// VDU 23,0,194,30
 #define VDP_LAYER_TILELAYER_FREE			0x1F		// VDU 23,0,194,31
 
+// Begin: Function Prototypes for internal Tile Engine use
+void debug_log_mem(void);
+// End: Function Prototypes for internal Tile Engine use
 
 void VDUStreamProcessor::vdu_sys_layers(void) {
 
@@ -212,13 +215,16 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_init(uint8_t tileBankNum, uint8
 	// Create a buffer of width, height and colour depth for 256 tiles
 	int tileBankBufferSize = tileBankTileWidth * tileBankTileHeight * 256;
 
+	debug_log("In vdu_sys_layers_tilebank_init: Before memory allocation\n\r");
+	debug_log_mem();
+
 	switch (tileBankNum) {		// Which tile bank is being initiated?
 
 		case 0: {				// Tile Bank 0
 			
 			// Check if already exists
 			
-			if (tileBank0Data !=NULL) {
+			if (tileBank0Data != NULL) {
 
 				// If already exists, then free and reallocate
 				vdu_sys_layers_tilebank_free(tileBankNum);
@@ -239,7 +245,8 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_init(uint8_t tileBankNum, uint8
 					*(tileBank0Ptr + i) = 0; 
 			}
 			else {
-				discardBytes(tileBankBufferSize);
+				// Something went wrong. Calll the free function to clear up the memory
+				vdu_sys_layers_tilebank_free(tileBankNum);
 			}
 		} break;
 
@@ -248,6 +255,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_init(uint8_t tileBankNum, uint8
 			return;
 		}
 	}
+
+	debug_log("In vdu_sys_layers_tilebank_init: After memory allocation\n\r");
+	debug_log_mem();
 }
 
 void VDUStreamProcessor::vdu_sys_layers_tilebank_load(uint8_t tileBankNum, uint8_t tileId) {
@@ -327,6 +337,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_draw(uint8_t tileBankNum, uint8
 
 void VDUStreamProcessor::vdu_sys_layers_tilebank_free(uint8_t tileBankNum) {
 
+	debug_log("In vdu_sys_layers_tilebank_free: Before memory free call\n\r");
+	debug_log_mem();
+
 	switch (tileBankNum) {
 		case 0: {
 			if (tileBank0Data != NULL) {
@@ -340,10 +353,16 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_free(uint8_t tileBankNum) {
 			debug_log("vdu_sys_layers_tilebank_free: Invalid tileBankNum %d specified.\r\n",tileBankNum);
 		}
 	}
+
+	debug_log("In vdu_sys_layers_tilebank_free: After memory free call\r\n");
+	debug_log_mem();
 }
 
 void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8_t tileMapSize) {
 
+	debug_log("In vdu_sys_layers_tilemap_init: Before memory allocation\n\r");
+	debug_log_mem();
+
 	// Check if tileMap0 already exists and free if it is.
 
 	switch (tileLayerNum) {
@@ -352,7 +371,7 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8
 			
 			// Check if already exists
 	
-			if (tileMap0 !=NULL) {
+			if (tileMap0 != NULL) {
 				// If already exists, then free and reinitialise
 
 				vdu_sys_layers_tilemap_free(tileLayerNum); 
@@ -445,6 +464,8 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8
 
 	int tileMapBufferSize = tileMapWidth * sizeof(struct Tile*);
 
+	bool tileMapMemoryAllocation = false;			// Flag to check memory allocation status. Default to false (i.e., not allocated).
+
 	// Initial release to only support a single tile layer
 	if (tileLayerNum != 0) {
 		debug_log("vdu_sys_layers_tilemap_init: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
@@ -455,18 +476,47 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8
 
 		case 0: {		// If tile map number is 0
 						
-			// Allocate memory for each column in the tile map
+			// Allocate memory wide enough for each column in the tile map
 			tileMap0 = (struct Tile**)heap_caps_malloc(tileMapBufferSize,MALLOC_CAP_SPIRAM);
 
-			// Allocate memory for each row in the tile map
-			for (auto i=0; i<tileMapWidth; i++) {
-				tileMap0[i] = (struct Tile*)heap_caps_malloc(tileMapHeight * sizeof(struct Tile),MALLOC_CAP_SPIRAM);
-			}
-						
-			// Only continue if the init was successful
-			
+			// If memory allocation for the width of the tilemap was a success, then allocate memory for each column in the tilemap
+
 			if (tileMap0 != NULL) {
 
+				// If tileMap0 is not null, then the memory has been successfully allocated
+
+				tileMapMemoryAllocation = true;
+
+				// As memory allocation was successful for the width of the tilemap, now allocate a column for each row
+
+				for (auto i=0; i<tileMapWidth; i++) {
+					tileMap0[i] = (struct Tile*)heap_caps_malloc(tileMapHeight * sizeof(struct Tile),MALLOC_CAP_SPIRAM);
+
+					// Check that the memory allocation for the row is successful
+					if (tileMap0[i] == NULL) {
+						debug_log("vdu_sys_layers_tilemap_init: Failed to allocate memory for tileMap0[%d].\r\n",tileMap0[i]);
+						tileMapMemoryAllocation = false;
+					}
+				}
+			} else {
+				debug_log("vdu_sys_layers_tilemap_init: Failed to allocate memory for tileMap0.\r\n");
+				
+				tileMapMemoryAllocation = false;
+				
+			}
+
+			// Check that memory allocation was successful. If not, then clean up. If successful, then set contents to 0.
+
+			if (tileMapMemoryAllocation == false) {
+
+				// Tidy up by calling free...
+
+				vdu_sys_layers_tilemap_free(tileLayerNum);
+
+			} else {
+
+				// Only continue if the init was successful...
+			
 				// Set every byte in the tile map to 0
 
 				for (auto i=0; i<tileMapWidth; i++) {
@@ -475,13 +525,14 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_init(uint8_t tileLayerNum, uint8
 						tileMap0[i][j].attribute = 0 ;
 					}
 				}
+
 			}
-			else {
-				debug_log("vdu_sys_layers_tilemap_init: Error when attempting to allocate memory for tilemap.\r\n");
-				discardBytes(tileMapBufferSize);
-			}
+
 		} break;
 	}
+
+	debug_log("In vdu_sys_layers_tilemap_init: After memory allocation\n\r");
+	debug_log_mem();
 }
 
 void VDUStreamProcessor::vdu_sys_layers_tilemap_set(uint8_t tileLayerNum, uint8_t xPos, uint8_t yPos, uint8_t tileId, uint8_t tileAttribute) {
@@ -508,6 +559,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_set(uint8_t tileLayerNum, uint8_
 
 void VDUStreamProcessor::vdu_sys_layers_tilemap_free(uint8_t tileLayerNum) {
 
+	debug_log("In vdu_sys_layers_tilemap_free: Before memory free call.\r\n");
+	debug_log_mem();
+
 	switch (tileLayerNum) {
 		case 0: {
 
@@ -518,19 +572,28 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_free(uint8_t tileLayerNum) {
 				debug_log("vdu_sys_layers_tilemap_free: Freeing tileMap0.\r\n");
 
 				for (auto i=0; i<tileMapWidth; i++) {
-					heap_caps_free(tileMap0[i]);
+
+					// For each column in the tilemap, free the memory if allocated
+					if (tileMap0[i] != NULL) {
+						heap_caps_free(tileMap0[i]);
+					}
 				}
 				heap_caps_free(tileMap0);
 
 				tileMap0 = NULL;
+
+			} else {
+				debug_log("vdu_sys_layers_tilemap_free: Tile Map %d memory not allocated.\r\n", tileLayerNum);
 			}
 		} break;
 
 		default: {
 			debug_log("vdu_sys_layers_tilemap_free: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
-			return;
 		}
-	}		
+	}	
+
+	debug_log("In vdu_sys_layers_tilemap_free: After memory free call.\r\n");
+	debug_log_mem();	
 }
 
 void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uint8_t tileLayerSize, uint8_t tileSize) {
@@ -1145,4 +1208,13 @@ void VDUStreamProcessor::writeTileToBufferFlipXY(uint8_t tileId, uint8_t tileCou
 	}
 }
 
+void debug_log_mem(void) {
+	debug_log("  free internal (MALLOC_CAP_INTERNAL): %d\n\r  free 8bit (MALLOC_CAP_8BIT): %d\n\r  free 32bit (MALLOC_CAP_32BIT): %d\n\r  PSRAM (MALLOC_CAP_SPIRAM): %d\n\r",
+		heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+		heap_caps_get_free_size(MALLOC_CAP_8BIT),
+		heap_caps_get_free_size(MALLOC_CAP_32BIT),
+		heap_caps_get_free_size(MALLOC_CAP_SPIRAM)
+	);
+}
+
 #endif // AGON_LAYERS_H
\ No newline at end of file

From b998cd32af0018d48d8e1de261e14ad520f2a22b Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Tue, 22 Oct 2024 22:25:31 +0100
Subject: [PATCH 3/6] Converted row buffer to layer buffer

---
 video/vdu_layers.h           | 1073 ++++++++++++++++++++++++++++++++--
 video/vdu_stream_processor.h |   15 +
 video/video.ino              |    2 +-
 3 files changed, 1024 insertions(+), 66 deletions(-)

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
index 53c819fd..785380ca 100644
--- a/video/vdu_layers.h
+++ b/video/vdu_layers.h
@@ -28,10 +28,13 @@
 #define VDP_LAYER_TILELAYER_INIT			0x18		// VDU 23,0,194,24
 #define VDP_LAYER_TILELAYER_SET_PROPERTY	0x19		// VDU 23,0,194,25
 #define VDP_LAYER_TILELAYER_SET_SCROLL		0x1A		// VDU 23,0,194,26
-#define VDP_LAYER_TILELAYER_SET_TABLE		0x1B		// VDU 23,0,194,27	[Future]
-#define VDP_LAYER_TILELAYER_SET_DRAW_PARAM	0x1D		// VDU 23,0,194,29 	[Future]
-#define VDP_LAYER_TILELAYER_DRAW			0x1E		// VDU 23,0,194,30
-#define VDP_LAYER_TILELAYER_FREE			0x1F		// VDU 23,0,194,31
+// #define VDP_LAYER_TILELAYER_SET_TABLE		0x1B		// VDU 23,0,194,27	[Future]
+// #define VDP_LAYER_TILELAYER_SET_DRAW_PARAM	0x1D		// VDU 23,0,194,29 	[Future]
+
+#define VDP_LAYER_TILELAYER_UPDATE_LAYERBUFFER	0x1C		// VDU 23,0,194,28 	[Future]
+#define VDP_LAYER_TILELAYER_DRAW_LAYERBUFFER	0x1D		// VDU 23,0,194,29 	[Future]
+#define VDP_LAYER_TILELAYER_DRAW				0x1E		// VDU 23,0,194,30
+#define VDP_LAYER_TILELAYER_FREE				0x1F		// VDU 23,0,194,31
 
 // Begin: Function Prototypes for internal Tile Engine use
 void debug_log_mem(void);
@@ -181,7 +184,30 @@ void VDUStreamProcessor::vdu_sys_layers(void) {
 
 		} break;
 
-		case VDP_LAYER_TILELAYER_SET_TABLE: {
+		//case VDP_LAYER_TILELAYER_SET_TABLE: {
+		//
+		//} break;
+
+		case VDP_LAYER_TILELAYER_UPDATE_LAYERBUFFER: {
+
+			// VDU 23,0,194,28,<tileLayerNum>
+			// Parameters are currently undefined but would facilitate drawing priority
+
+			uint8_t tileLayerNum = readByte_t();
+
+			vdu_sys_layers_tilelayer_update_layerbuffer(tileLayerNum);
+
+		} break;
+
+
+		case VDP_LAYER_TILELAYER_DRAW_LAYERBUFFER: {
+
+			// VDU 23,0,194,29,<tileLayerNum>
+			// Parameters are currently undefined but would facilitate drawing priority
+
+			uint8_t tileLayerNum = readByte_t();
+
+			vdu_sys_layers_tilelayer_draw_layerbuffer(tileLayerNum);
 
 		} break;
 
@@ -197,8 +223,11 @@ void VDUStreamProcessor::vdu_sys_layers(void) {
 		} break;
 
 		case VDP_LAYER_TILELAYER_FREE: {
-			// Not required? We don't dynamically allocate memory in VDP_LAYER_TILELAYER_INIT
-			// so there is nothing to free?
+		
+			uint8_t tileLayerNum = readByte_t();
+
+			vdu_sys_layers_tilelayer_free(tileLayerNum);
+
 		} break;
 	}
 }
@@ -598,6 +627,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilemap_free(uint8_t tileLayerNum) {
 
 void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uint8_t tileLayerSize, uint8_t tileSize) {
 	
+	debug_log("In vdu_sys_layers_tilelayer_init: Before memory allocation\n\r");
+	debug_log_mem();
+
 	uint8_t tileLayerHeight;
 	uint8_t tileLayerWidth;
 
@@ -637,22 +669,6 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uin
 		}
 	}
 
-	int rowBufferWidth = tileLayerWidth * 8;						// Number of bytes (pixels) wide
-
-	int rowBufferHeight = 8;										// Number of scanlines in the buffer
-
-	int rowDataBufferSize = rowBufferWidth * rowBufferHeight;		// Size of the buffer in bytes
-
-	// Create the row bitmap
-
-	currentRow = Bitmap(rowBufferWidth, rowBufferHeight, currentRowDataBuffer, PixelFormat::RGBA2222);
-
-	// Zero out the active parts of the buffer
-
-	for (auto i=0; i<rowDataBufferSize; i++) {
-		currentRowDataBuffer[i] = 0;
-	}
-
 	switch (tileLayerNum) {
 
 		case 0: {		// Tile Layer 0
@@ -665,6 +681,49 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uin
 			tileLayer0.yOffset = 0;
 			tileLayer0.attribute = 0;
 
+			if (tileLayer0Buffer != NULL) {
+
+				// If already exists, then free and reallocate
+				vdu_sys_layers_tilelayer_free(tileLayerNum);
+			}
+
+			int tileLayer0BufferSize = ((tileLayerHeight + 1) * 8) * ((tileLayerWidth + 1) * 8);
+			// int tileLayer0BufferSize = ((tileLayerHeight) * 8) * ((tileLayerWidth) * 8);
+
+			debug_log("In vdu_sys_layers_tilelayer_init: tileLayerHeight: %d tileLayerWidth: %d\r\n", tileLayerHeight, tileLayerWidth);
+			debug_log("In vdu_sys_layers_tilelayer_init: tileLayer0BufferSize: %dbytes (%dK)\r\n", tileLayer0BufferSize, tileLayer0BufferSize / 1024);
+
+			tileLayer0Buffer = heap_caps_malloc(tileLayer0BufferSize,MALLOC_CAP_SPIRAM);
+
+
+			// Log the allocated memory
+
+
+			if (tileLayer0Buffer != nullptr) {
+				size_t actualSize = heap_caps_get_allocated_size(tileLayer0Buffer);
+				debug_log("Allocated size: %zu bytes\r\n", actualSize);
+			} else {
+				debug_log("Memory allocation failed\r\n");
+			}
+
+			debug_log("In vdu_sys_layers_tilelayer_init: tileLayer0Buffer starting address: %d\r\n", &tileLayer0Buffer);
+
+			if (tileLayer0Buffer != NULL) {
+
+				// Cast the void pointer to an integer
+				tileLayer0Ptr = (uint8_t *)tileLayer0Buffer;
+
+				// Set every byte in the tile bank to the background colour of the layer (default 0 = transparent)
+				for (auto i=0; i <tileLayer0BufferSize; i++)
+					tileLayer0Ptr[i] = tileLayer0.backgroundColour; 
+
+				tileLayer0Bitmap = Bitmap(tileLayerWidth * 8, tileLayerHeight * 8, tileLayer0Buffer, PixelFormat::RGBA2222);
+			}
+			else {
+				// Something went wrong. Calll the free function to clear up the memory
+				vdu_sys_layers_tilelayer_free(tileLayerNum);
+			}
+
 			tileLayer0init = 1;		// Set as initialised
 
 		} break;
@@ -675,6 +734,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uin
 		}
 
 	}
+
+	debug_log("In vdu_sys_layers_tilelayer_init: After memory allocation\n\r");
+	debug_log_mem();
 }
 
 void VDUStreamProcessor::vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNum, uint8_t xPos, uint8_t yPos, uint8_t xOffset, uint8_t yOffset) {
@@ -717,17 +779,13 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNu
 	}
 }
 
-void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
-
-	// Timing debug statements
-
-	// auto startTime = xTaskGetTickCountFromISR();
-  	// auto endTime = xTaskGetTickCountFromISR();
-	// auto sumTime = xTaskGetTickCountFromISR();
-	// sumTime = 0;
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t tileLayerNum) {
 
 	int xPix = 0;		// X position in pixels is now always 0 as the offset is written directly to the tileRowBuffer
-	int yPix = 0;
+	int yPix = 0;		// Y position in pixels is also always 0 as the displayed bitmap is the entire layer size.
+
+	uint8_t xPos;
+	uint8_t yPos;
 
 	uint8_t tileId;
 	uint8_t tileAttribute;
@@ -760,24 +818,24 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
 					tileMapWidth = tileMap0Properties.width;
 					tileMapHeight = tileMap0Properties.height;
 				} else {
-					debug_log("vdu_sys_layers_tilelayer_draw: tileMap0 is not initialised.\r\n");
+					debug_log("vdu_sys_layers_tilelayer_renderlayer: tileMap0 is not initialised.\r\n");
 					return;
 				}
 			} else {
-				debug_log ("vdu_sys_layers_tilelayer_draw: tileLayer0 is not initialised.\r\n");
+				debug_log ("vdu_sys_layers_tilelayer_renderlayer: tileLayer0 is not initialised.\r\n");
 				return;
 			}
 		} break;
 
 		default: {
-			debug_log("Invalid tileLayerNum: %d\r\n",tileLayerNum);
+			debug_log("vdu_sys_layers_tilelayer_renderlayer: Invalid tileLayerNum: %d\r\n",tileLayerNum);
 			return;
 		}
 	}
 
-	int rowBufferWidth = tileLayerWidth * 8;
-	int rowBufferHeight = 8;
-	int rowDataBufferSize = rowBufferWidth * rowBufferHeight;
+	int layerBufferWidth = tileLayerWidth * 8;
+	int layerBufferHeight = tileLayerHeight * 8;
+	int layerDataBufferSize = layerBufferWidth * layerBufferHeight;
 
 	// Perform validation checks
 
@@ -786,22 +844,26 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
 
 	// Do not continue if tileBank is not initialised.
 	if (tileBank0Data == NULL) { 
-		debug_log("vdu_sys_layers_tilelayer_draw: tileBank0Data is not initialised.\r\n");
+		debug_log("vdu_sys_layers_tilelayer_renderlayer: tileBank0Data is not initialised.\r\n");
 		return;
 	}
 
-	// Process rows for each frame
+	// Clear Row Buffer - move this up...
+	
+		for (auto i=0; i<layerDataBufferSize; i++) {
+			tileLayer0Ptr[i] = 0;				// Setting to 0 is transparent. Future: Layer BG Colour setting.
+	}
 
-	for (auto y=0; y<=tileLayerHeight; y++) {				
+	// Process rows for each frame
 
-		// Clear Row Buffer
+	for (auto y=0; y<=tileLayerHeight; y++) {	// This may be causing the crash if the layer buffer needs to be one row bigger than the layer...?
 
-		for (auto i=0; i<rowDataBufferSize; i++) {
-			currentRowDataBuffer[i] = 0;
-		}
+	// for (auto y=0; y<tileLayerHeight; y++) {				
 
 		// Process tile map for current row
 
+		yPos = y;
+
 		for (auto x=0; x<=tileLayerWidth; x++) {
 																
 			// read the Tile Map
@@ -810,7 +872,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
 
 			tileAttribute = tileMap0[sourceXPos][sourceYPos].attribute;
 
-			tileRowOffset = x;	
+			// tileRowOffset = x;
+
+			xPos = x;	
 
 			if (tileId == 0) {		// Tile 0 is special...
 
@@ -830,16 +894,16 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
 
 				switch (tileFlip) {
 					case 0x00:		// Normal drawing
-						writeTileToBuffer(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						writeTileToLayerBuffer(tileId, xPos, xOffset, yPos, yOffset, tileLayer0Ptr, tileLayerHeight, tileLayerWidth);
 						break;
 					case 0x01:		// Flip X
-						writeTileToBufferFlipX(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						writeTileToLayerBufferFlipX(tileId, xPos, xOffset, yPos, yOffset, tileLayer0Ptr, tileLayerHeight, tileLayerWidth);
 						break;
 					case 0x02:		// Flip Y
-						writeTileToBufferFlipY(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						writeTileToLayerBufferFlipY(tileId, xPos, xOffset, yPos, yOffset, tileLayer0Ptr, tileLayerHeight, tileLayerWidth);
 						break;
 					case 0x03:		// Flip X and Y
-						writeTileToBufferFlipXY(tileId, tileRowOffset, xOffset, currentRowDataBuffer, tileLayerWidth);
+						writeTileToLayerBufferFlipXY(tileId, xPos, xOffset, yPos, yOffset, tileLayer0Ptr, tileLayerHeight, tileLayerWidth);
 						break;
 					default:
 						debug_log("Invalid tileAttribute value: %d\r\n",tileFlip);
@@ -858,33 +922,915 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
 		sourceXPos = tileLayer0.sourceXPos;
 		
 		// Set Y position to display on screen
-		yPix = (y * 8) - tileLayer0.yOffset;		// Y position in pixels 
+		// yPix = (y * 8) - tileLayer0.yOffset;		// Y position in pixels 		// Commented out as layer buffer will use the whole screen and needs to be positioned at 0
 	
-		// Draw Row
+		sourceYPos++;
+		if (sourceYPos == tileMapHeight) {
+			sourceYPos = 0; 
+		}
+	}
 
-		// startTime = xTaskGetTickCountFromISR();
+}
 
-		currentRow = Bitmap(rowBufferWidth, rowBufferHeight, currentRowDataBuffer, PixelFormat::RGBA2222);
 
-		canvas->drawBitmap(xPix,yPix,&currentRow);		
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileLayerNum) {
 
-		waitPlotCompletion();
+	// Timing debug statements
 
-		// endTime = xTaskGetTickCountFromISR();
-		// auto elapsedTime = endTime - startTime;
-		// sumTime = sumTime + elapsedTime;
+	// auto startTime = xTaskGetTickCountFromISR();
+  	// auto endTime = xTaskGetTickCountFromISR();
+	// auto sumTime = xTaskGetTickCountFromISR();
+	// sumTime = 0;
 
-		sourceYPos++;
-		if (sourceYPos == tileMapHeight) {
-			sourceYPos = 0; 
+	int xPix = 0;		// X position in pixels is now always 0 as the offset is written directly to the tileRowBuffer
+	int yPix = 0;
+
+	uint8_t xPos;
+	uint8_t yPos;
+
+	uint8_t tileId;
+	uint8_t tileAttribute;
+	uint8_t tileLayerHeight;
+	uint8_t tileLayerWidth;
+	uint8_t sourceXPos;
+	uint8_t sourceYPos;
+	uint8_t xOffset;
+	uint8_t yOffset;
+	uint8_t tileMapWidth;
+	uint8_t tileMapHeight;
+	uint8_t tileRowOffset = 0;
+
+	switch (tileLayerNum) {
+
+		case 0: {
+
+			if (tileLayer0init != 0) {
+				if (tileMap0 != NULL) {
+
+					tileLayerHeight = tileLayer0.height;
+					tileLayerWidth = tileLayer0.width;
+
+					sourceXPos = tileLayer0.sourceXPos;
+					sourceYPos = tileLayer0.sourceYPos;
+
+					xOffset = tileLayer0.xOffset;
+					yOffset = tileLayer0.yOffset;
+
+					tileMapWidth = tileMap0Properties.width;
+					tileMapHeight = tileMap0Properties.height;
+				} else {
+					debug_log("vdu_sys_layers_tilelayer_renderlayer: tileMap0 is not initialised.\r\n");
+					return;
+				}
+			} else {
+				debug_log ("vdu_sys_layers_tilelayer_renderlayer: tileLayer0 is not initialised.\r\n");
+				return;
+			}
+		} break;
+
+		default: {
+			debug_log("vdu_sys_layers_tilelayer_renderlayer: Invalid tileLayerNum: %d\r\n",tileLayerNum);
+			return;
 		}
 	}
 
+	int layerBufferWidth = tileLayerWidth * 8;
+	int layerBufferHeight = tileLayerHeight * 8;
+	int layerDataBufferSize = layerBufferWidth * layerBufferHeight;
+
+	// Perform validation checks
+
+	if (sourceXPos >= tileMapWidth) { sourceXPos = 0; }
+	if (sourceYPos >= tileMapHeight) { sourceYPos = 0; }
+
+	// Do not continue if tileBank is not initialised.
+	if (tileBank0Data == NULL) { 
+		debug_log("vdu_sys_layers_tilelayer_renderlayer: tileBank0Data is not initialised.\r\n");
+		return;
+	}
+
+	// startTime = xTaskGetTickCountFromISR();
+
+	tileLayer0Bitmap = Bitmap(layerBufferWidth, layerBufferHeight, tileLayer0Ptr, PixelFormat::RGBA2222);
+
+	canvas->drawBitmap(xPix,yPix,&tileLayer0Bitmap);		
+
+	waitPlotCompletion();
+
+	// endTime = xTaskGetTickCountFromISR();
+	// auto elapsedTime = endTime - startTime;
+	// sumTime = sumTime + elapsedTime;
+
 	// debug_log("vdu_sys_layers_tilelayer_draw: Sum time is %d\r\n",sumTime);
 }
 
+
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum) {
+
+	// auto startTime = xTaskGetTickCountFromISR();
+  	// auto endTime = xTaskGetTickCountFromISR();
+	// unsigned int elapsedTime;
+
+	// startTime = xTaskGetTickCountFromISR();
+
+	vdu_sys_layers_tilelayer_update_layerbuffer(tileLayerNum);
+
+	// endTime = xTaskGetTickCountFromISR();
+	// elapsedTime = endTime - startTime;
+	// debug_log("In vdu_sys_layers_tilelayer_draw: Tick rate: %dHz\r\n", configTICK_RATE_HZ);
+	// debug_log("In vdu_sys_layers_tilelayer_draw: vdu_sys_layers_tilelayer_update_layerbuffer() execution time: %d\r\n",elapsedTime);
+
+	// startTime = xTaskGetTickCountFromISR();
+
+	vdu_sys_layers_tilelayer_draw_layerbuffer(tileLayerNum);
+
+	// endTime = xTaskGetTickCountFromISR();
+	// elapsedTime = endTime - startTime;
+	// debug_log("In vdu_sys_layers_tilelayer_draw: vdu_sys_layers_tilelayer_draw_layerbuffer() execution time: %d\r\n",elapsedTime);
+
+
+}
+
+void VDUStreamProcessor::vdu_sys_layers_tilelayer_free(uint8_t tileLayerNum) {
+
+	debug_log("In vdu_sys_layers_tilelayer_free: Before memory free call\n\r");
+	debug_log_mem();
+
+	switch (tileLayerNum) {
+		case 0: {
+			if (tileLayer0Buffer != NULL) {
+				debug_log("vdu_sys_layers_tilelayer_free: Freeing tileLayer0Buffer.\r\n");
+				heap_caps_free(tileLayer0Buffer);
+				tileLayer0Buffer = NULL;
+			}
+		} break;
+		
+		default: {
+			debug_log("vdu_sys_layers_tilelayer_free: Invalid tileLayerNum %d specified.\r\n",tileLayerNum);
+		}
+	}
+
+	debug_log("In vdu_sys_layers_tilelayer_free: After memory free call\r\n");
+	debug_log_mem();
+}
+
 // Tile drawing functions
 
+void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth) {
+
+/*
+		The drawing loop reads from the source in a direction depending on the flip parameters:
+		- For normal drawing, read from top row to bottom row, left to right.
+		- For flip X drawing, read from top row to bottom row, right to left.
+		- For flip Y drawing, read from bottom row to top row, left to right.
+		- For flip X+Y drawing, read from bottom row to top row, right to left.
+		
+		Writes to the layer buffer are in a linear fashion: top row to bottom row, left to right.
+
+		The drawing loops for rows and columns (as defined by variables x and y) are intended to be
+		the counters for reading the *source* data. While there may be some cases where this can also
+		apply to the destination data, a separate set of variables are used to ensure clarity.
+	*/
+
+	int sourceTile = (tileId * 64);																	// The starting position in the tile bank for a given tileId;
+	int sourcePixel = 0;
+	int tileLayerPixelWidth = tileLayerWidth * 8;													// The width of the tile layer in pixels
+	int destLineStart = (yPos * tileLayerPixelWidth * 8 ) - (tileLayerPixelWidth * yOffset);		// The line where drawing the tile should be drawn
+	int destLineStartXOffset = (xPos * 8) - xOffset;												// The number of pixels along the line where the tile should be drawn
+	int destLineStartYOffset = 0;																	// The line in the tile (0-7) where the tile should be drawn
+	int destPixel;
+	int destPixelCount;
+	int destPixelStart;
+
+	/* 
+	Set the start and end variables used in the source reading loop depending on whether it is the first, middle or last column:
+	- For the first column (0), then potentially only read the last "n" pixels in the tile row.
+	- For columns in the middle (1 through to tileLayerWidth -1), then read all eight pixels in the tile row.
+	- For the last column (tileLayerWidth), then potentially only read the first "n" pixels. 
+	*/
+
+	if (yPos > 0 && yPos < tileLayerHeight) {
+
+		if (xPos > 0 && xPos < tileLayerWidth) {		// Process the most common use-case (middle tiles) first
+
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}  			
+		} else if (xPos == 0) {							// Process the first column
+
+			destLineStartXOffset = 0;
+
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		} else if (xPos == tileLayerWidth) {			// Process the last column
+		
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}
+		}
+
+	} else if (yPos == 0) {
+
+		// Do the top row
+
+		if (xPos > 0 && xPos < tileLayerWidth) {		// Process the most common use-case (middle tiles) first
+
+			for (auto y=yOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}
+
+		} else if (xPos == 0) {							// Process the first column
+
+	 		destLineStartXOffset = 0;
+
+ 			for (auto y=yOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		} else if (xPos == tileLayerWidth) {			// Process the last column
+		
+ 			for (auto y=yOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}  
+		}
+
+
+	} else if (yPos == tileLayerHeight) {
+
+		// Do the bottom row
+
+		if (xPos > 0 && xPos < tileLayerWidth) {		// Process the most common use-case (middle tiles) first
+
+		 	for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+
+			// debug_log("Bottom Row Middle: destPixel address: %d\r\n", &tileBuffer[destPixel]);
+
+		} else if (xPos == 0) {							// Process the first column
+
+			destLineStartXOffset = 0;
+
+			for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+
+			// debug_log("Bottom Row Column 0: destPixel address: %d\r\n", &tileBuffer[destPixel]);
+
+		} else if (xPos == tileLayerWidth) {			// Process the last column
+	
+			for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}
+
+			// The output will be random if y and x never match and the loops never execute.
+			// debug_log("Bottom Row End: destPixel address: %d\r\n", &tileBuffer[destPixel]);
+		} 
+	}
+}
+
+void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth) {
+
+	int sourceTile = (tileId * 64);
+	int sourcePixel = 0;
+	int tileLayerPixelWidth = tileLayerWidth * 8;
+	int destLineStart = (yPos * tileLayerPixelWidth * 8 ) - (tileLayerPixelWidth * yOffset);
+	int destLineStartXOffset = (xPos * 8) - xOffset;
+	int destLineStartYOffset = 0;	
+	int destPixel;
+	int destPixelCount;
+	int destPixelStart;
+
+	if (yPos > 0 && yPos < tileLayerHeight) {
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}  
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;
+
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=0; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		}
+	
+	}  else if (yPos == 0) {
+
+		// Do the top row
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+		for (auto y=yOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}
+			
+		} else if (xPos == 0) {
+
+ 			destLineStartXOffset = 0;
+
+			for (auto y=xOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}
+
+		} else if (xPos == tileLayerWidth) {
+
+ 			for (auto y=xOffset; y<8; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}  
+		}
+
+	} else if (yPos == tileLayerHeight) {
+
+		// Do the bottom row
+		
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			}  
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;
+
+			for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=0; y<=yOffset; y++) {
+
+				destLineStartYOffset = tileLayerPixelWidth * y;
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+			} 
+		} 
+	}	
+}
+
+void VDUStreamProcessor::writeTileToLayerBufferFlipY(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth) {
+ 
+	int sourceTile = (tileId * 64);
+	int sourcePixel = 0;
+	int tileLayerPixelWidth = tileLayerWidth * 8;
+	int destLineStart = (yPos * tileLayerPixelWidth * 8 ) - (tileLayerPixelWidth * yOffset);
+	int destLineStartXOffset = (xPos * 8) - xOffset;
+	int destLineStartYOffset = 0;								// Start writing from the top line
+	int destPixel;
+	int destPixelCount;
+	int destPixelStart;
+	
+	if (yPos > 0 && yPos < tileLayerHeight) {
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}	
+
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;							// Need to override destStartLineXOffset to start at column 0 with no xOffset
+					
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;														// Need to track destination pixel counter as x starts with an offset
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;		// Update destLineStartYOffset to the next line.
+			}
+
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;	
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + destPixelCount;
+					destPixel = destPixelStart + x;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}
+		}	
+
+	} else if (yPos == 0) {
+
+	 	// Do the top row
+
+		destLineStart = 0;		// We are only reading a smaller number of lines, so start from 0, not a negative start y position.
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}	 
+
+		} else if (xPos == 0) {
+
+ 			destLineStartXOffset = 0;							// Need to override destStartLineXOffset to start at column 0 with no xOffset
+					
+			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;														// Need to track destination pixel counter as x starts with an offset
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;		// Update destLineStartYOffset to the next line.
+			}
+
+		} else if (xPos == tileLayerWidth) {
+
+ 			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;	
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + destPixelCount;
+					destPixel = destPixelStart + x;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}  
+		}
+				
+	} else if (yPos == tileLayerHeight) {
+
+		// Do the bottom row
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=0; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}	
+
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;							// Need to override destStartLineXOffset to start at column 0 with no xOffset
+					
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;														// Need to track destination pixel counter as x starts with an offset
+
+				for (auto x=xOffset; x<8; x++) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;		// Update destLineStartYOffset to the next line.
+			}
+
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;	
+
+				for (auto x=0; x<xOffset; x++) {
+					sourcePixel = sourceTile + (y * 8) + destPixelCount;
+					destPixel = destPixelStart + x;
+					tileBuffer[destPixel]=tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}
+		} 	
+	}
+}
+
+void VDUStreamProcessor::writeTileToLayerBufferFlipXY(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth) {
+ 
+	int sourceTile = (tileId * 64);	
+	int sourcePixel = 0;
+	int tileLayerPixelWidth = tileLayerWidth * 8;
+	int destLineStart = (yPos * tileLayerPixelWidth * 8 ) - (tileLayerPixelWidth * yOffset);
+	int destLineStartXOffset = (xPos * 8) - xOffset;
+	int destLineStartYOffset = 0;
+	int destPixel;
+	int destPixelCount = 0;
+	int destPixelStart;
+
+	if (yPos > 0 && yPos < tileLayerHeight) {
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}  
+
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;
+
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			} 
+
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=7; y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}
+		}
+
+	} else if (yPos == 0) {
+
+		// Do the top row
+
+		destLineStart = 0;
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+ 			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}  
+
+		} else if (xPos == 0) {
+
+		 	destLineStartXOffset = 0;
+
+			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}
+
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=(7-yOffset); y>=0; y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}
+		}
+
+	} else if (yPos == tileLayerHeight) {
+
+		// Do the bottom row
+
+		if (xPos > 0 && xPos < tileLayerWidth) {
+
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			}  
+
+		} else if (xPos == 0) {
+
+			destLineStartXOffset = 0;
+
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=(7-xOffset); x>=0; x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			} 
+
+		} else if (xPos == tileLayerWidth) {
+
+			for (auto y=7; y>=(7-yOffset); y--) {
+
+				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
+				destPixelCount = 0;
+
+				for (auto x=7; x>(7-xOffset); x--) {
+					sourcePixel = sourceTile + (y * 8) + x;
+					destPixel = destPixelStart + destPixelCount;
+					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
+					destPixelCount++;
+				}
+
+				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
+			} 
+		}
+
+	}
+}
+
+
+
 void VDUStreamProcessor::writeTileToBuffer(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth) {
 
 	int destStartPos;
@@ -1076,7 +2022,6 @@ void VDUStreamProcessor::writeTileToBufferFlipY(uint8_t tileId, uint8_t tileCoun
 			destRowCount++;
 			destPixel = destPixel + xOffset;
 		}
-
 	}
 
 	// General drawing code for all columns except the first and last columns
@@ -1099,7 +2044,6 @@ void VDUStreamProcessor::writeTileToBufferFlipY(uint8_t tileId, uint8_t tileCoun
 
 			destRowCount++;
 		}	
-
 	}
 
 	// Handle the final column on screen which may only be partially drawn depending on the xOffset value
@@ -1204,7 +2148,6 @@ void VDUStreamProcessor::writeTileToBufferFlipXY(uint8_t tileId, uint8_t tileCou
 			destRowCount++;
 			destPixel = destPixel + xOffset;
 		}
-
 	}
 }
 
diff --git a/video/vdu_stream_processor.h b/video/vdu_stream_processor.h
index d26bf2ec..853d4daf 100644
--- a/video/vdu_stream_processor.h
+++ b/video/vdu_stream_processor.h
@@ -152,12 +152,20 @@ class VDUStreamProcessor {
 		void vdu_sys_layers_tilemap_free(uint8_t tileMapNum);
 		void vdu_sys_layers_tilelayer_init(uint8_t tileLayerNum, uint8_t tileLayerSize, uint8_t tileSize);
 		void vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNum, uint8_t x, uint8_t y, uint8_t xOffset, uint8_t yOffset);
+		void vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t tileLayerNum);
+		void vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileLayerNum);
 		void vdu_sys_layers_tilelayer_draw(uint8_t tileLayerNum);
+		void vdu_sys_layers_tilelayer_free(uint8_t tileLayerNum);
 		void writeTileToBuffer(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
 		void writeTileToBufferFlipX(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
 		void writeTileToBufferFlipY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
 		void writeTileToBufferFlipXY(uint8_t tileId, uint8_t tileCount, uint8_t xOffset, uint8_t tileBuffer[], uint8_t tileLayerWidth);
 
+		void writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer,  uint8_t tileLayerHeight, uint8_t tileLayerWidth);
+		void writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth);
+		void writeTileToLayerBufferFlipY(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth);
+		void writeTileToLayerBufferFlipXY(uint8_t tileId, uint8_t xPos, uint8_t xOffset, uint8_t yPos, uint8_t yOffset, uint8_t * tileBuffer, uint8_t tileLayerHeight, uint8_t tileLayerWidth);
+
 		// Tile Bank variables
 
 		void * tileBank0Data = NULL;
@@ -187,6 +195,12 @@ class VDUStreamProcessor {
 		Bitmap currentRow;
 		uint8_t currentRowDataBuffer[5184];		// Buffer big enough for 64 byte tiles * 81 columns (the largest supported size +1)
 
+		Bitmap tileLayer0Bitmap;					// Bitmap that points to the Layer 0 buffer
+
+		void * tileLayer0Buffer = NULL;				// The offscreen buffer for Layer 0
+
+		uint8_t * tileLayer0Ptr;					// A pointer to he tileLayer0Buffer
+
 		struct TileLayer {
 			uint8_t height;
 			uint8_t width;
@@ -195,6 +209,7 @@ class VDUStreamProcessor {
 			uint8_t xOffset;
 			uint8_t yOffset;
 			uint8_t attribute;
+			uint8_t backgroundColour = 0;			// Default the background colour of the layer to 0 (transparent)
 		};
 
 		TileLayer tileLayer0;
diff --git a/video/video.ino b/video/video.ino
index 431843cb..5cac3c35 100644
--- a/video/video.ino
+++ b/video/video.ino
@@ -50,7 +50,7 @@
 #include <WiFi.h>
 #include <fabgl.h>
 
-#define	DEBUG			0						// Serial Debug Mode: 1 = enable
+#define	DEBUG			1						// Serial Debug Mode: 1 = enable
 #define SERIALBAUDRATE	115200
 
 HardwareSerial	DBGSerial(0);

From 7cb6c6b9da233ce7df6be31be3dd8249fb885040 Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Thu, 31 Oct 2024 20:02:44 +0000
Subject: [PATCH 4/6] Tidied layer drawing function

---
 video/vdu_layers.h | 81 +++++++++-------------------------------------
 1 file changed, 15 insertions(+), 66 deletions(-)

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
index 785380ca..defe9f5d 100644
--- a/video/vdu_layers.h
+++ b/video/vdu_layers.h
@@ -357,7 +357,7 @@ void VDUStreamProcessor::vdu_sys_layers_tilebank_draw(uint8_t tileBankNum, uint8
 
 		canvas->drawBitmap(xPix,yPix,&currentTile);
 
-		waitPlotCompletion();
+		waitPlotCompletion();		// If this is not set then tiles do not display correctly if called rapidly.
 
 	} else {
 		debug_log("vdu_sys_layers_tilebank_draw: tileBank0Data not initialised.\r\n");
@@ -856,9 +856,7 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 
 	// Process rows for each frame
 
-	for (auto y=0; y<=tileLayerHeight; y++) {	// This may be causing the crash if the layer buffer needs to be one row bigger than the layer...?
-
-	// for (auto y=0; y<tileLayerHeight; y++) {				
+	for (auto y=0; y<=tileLayerHeight; y++) {
 
 		// Process tile map for current row
 
@@ -867,13 +865,9 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 		for (auto x=0; x<=tileLayerWidth; x++) {
 																
 			// read the Tile Map
-
 			tileId = tileMap0[sourceXPos][sourceYPos].id;
-
 			tileAttribute = tileMap0[sourceXPos][sourceYPos].attribute;
 
-			// tileRowOffset = x;
-
 			xPos = x;	
 
 			if (tileId == 0) {		// Tile 0 is special...
@@ -911,7 +905,6 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 			}
 
 			// If we're at the edge of the tile map, reset to the beginning.
-
 			sourceXPos++;
 			if (sourceXPos == tileMapWidth) { 
 				sourceXPos = 0; 
@@ -920,45 +913,22 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 
 		// At the end of the row, reset sourceXPos back (else it will keep incrementing)
 		sourceXPos = tileLayer0.sourceXPos;
-		
-		// Set Y position to display on screen
-		// yPix = (y * 8) - tileLayer0.yOffset;		// Y position in pixels 		// Commented out as layer buffer will use the whole screen and needs to be positioned at 0
 	
 		sourceYPos++;
 		if (sourceYPos == tileMapHeight) {
 			sourceYPos = 0; 
 		}
 	}
-
 }
 
 
 void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileLayerNum) {
 
-	// Timing debug statements
-
-	// auto startTime = xTaskGetTickCountFromISR();
-  	// auto endTime = xTaskGetTickCountFromISR();
-	// auto sumTime = xTaskGetTickCountFromISR();
-	// sumTime = 0;
-
 	int xPix = 0;		// X position in pixels is now always 0 as the offset is written directly to the tileRowBuffer
 	int yPix = 0;
 
-	uint8_t xPos;
-	uint8_t yPos;
-
-	uint8_t tileId;
-	uint8_t tileAttribute;
 	uint8_t tileLayerHeight;
 	uint8_t tileLayerWidth;
-	uint8_t sourceXPos;
-	uint8_t sourceYPos;
-	uint8_t xOffset;
-	uint8_t yOffset;
-	uint8_t tileMapWidth;
-	uint8_t tileMapHeight;
-	uint8_t tileRowOffset = 0;
 
 	switch (tileLayerNum) {
 
@@ -970,14 +940,6 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileL
 					tileLayerHeight = tileLayer0.height;
 					tileLayerWidth = tileLayer0.width;
 
-					sourceXPos = tileLayer0.sourceXPos;
-					sourceYPos = tileLayer0.sourceYPos;
-
-					xOffset = tileLayer0.xOffset;
-					yOffset = tileLayer0.yOffset;
-
-					tileMapWidth = tileMap0Properties.width;
-					tileMapHeight = tileMap0Properties.height;
 				} else {
 					debug_log("vdu_sys_layers_tilelayer_renderlayer: tileMap0 is not initialised.\r\n");
 					return;
@@ -996,12 +958,6 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileL
 
 	int layerBufferWidth = tileLayerWidth * 8;
 	int layerBufferHeight = tileLayerHeight * 8;
-	int layerDataBufferSize = layerBufferWidth * layerBufferHeight;
-
-	// Perform validation checks
-
-	if (sourceXPos >= tileMapWidth) { sourceXPos = 0; }
-	if (sourceYPos >= tileMapHeight) { sourceYPos = 0; }
 
 	// Do not continue if tileBank is not initialised.
 	if (tileBank0Data == NULL) { 
@@ -1009,19 +965,12 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_draw_layerbuffer(uint8_t tileL
 		return;
 	}
 
-	// startTime = xTaskGetTickCountFromISR();
-
 	tileLayer0Bitmap = Bitmap(layerBufferWidth, layerBufferHeight, tileLayer0Ptr, PixelFormat::RGBA2222);
 
 	canvas->drawBitmap(xPix,yPix,&tileLayer0Bitmap);		
 
-	waitPlotCompletion();
+	// waitPlotCompletion();			// If enabled, then the code waits for VSYNC before continuing and is slower.
 
-	// endTime = xTaskGetTickCountFromISR();
-	// auto elapsedTime = endTime - startTime;
-	// sumTime = sumTime + elapsedTime;
-
-	// debug_log("vdu_sys_layers_tilelayer_draw: Sum time is %d\r\n",sumTime);
 }
 
 
@@ -1118,7 +1067,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 				destLineStartYOffset = tileLayerPixelWidth * y;
 				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
 				destPixelCount = 0;
-
+				
 				for (auto x=0; x<8; x++) {
 					sourcePixel = sourceTile + (y * 8) + x;
 					destPixel = destPixelStart + destPixelCount;
@@ -1196,7 +1145,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 		} else if (xPos == tileLayerWidth) {			// Process the last column
 		
  			for (auto y=yOffset; y<8; y++) {
@@ -1211,7 +1160,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			}  
+			} 
 		}
 
 
@@ -1233,7 +1182,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 
 			// debug_log("Bottom Row Middle: destPixel address: %d\r\n", &tileBuffer[destPixel]);
 
@@ -1253,7 +1202,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 
 			// debug_log("Bottom Row Column 0: destPixel address: %d\r\n", &tileBuffer[destPixel]);
 
@@ -1339,7 +1288,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPo
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 		}
 	
 	}  else if (yPos == 0) {
@@ -1348,7 +1297,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPo
 
 		if (xPos > 0 && xPos < tileLayerWidth) {
 
-		for (auto y=yOffset; y<8; y++) {
+			for (auto y=yOffset; y<8; y++) {
 
 				destLineStartYOffset = tileLayerPixelWidth * y;
 				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
@@ -1394,7 +1343,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPo
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			}  
+			}
 		}
 
 	} else if (yPos == tileLayerHeight) {
@@ -1432,7 +1381,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPo
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 		} else if (xPos == tileLayerWidth) {
 
 			for (auto y=0; y<=yOffset; y++) {
@@ -1447,7 +1396,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipX(uint8_t tileId, uint8_t xPo
 					tileBuffer[destPixel] = tileBank0Ptr[sourcePixel];
 					destPixelCount++;
 				}
-			} 
+			}
 		} 
 	}	
 }
@@ -1729,7 +1678,7 @@ void VDUStreamProcessor::writeTileToLayerBufferFlipXY(uint8_t tileId, uint8_t xP
 				}
 
 				destLineStartYOffset = destLineStartYOffset + tileLayerPixelWidth;
-			}  
+			} 
 
 		} else if (xPos == 0) {
 
@@ -2160,4 +2109,4 @@ void debug_log_mem(void) {
 	);
 }
 
-#endif // AGON_LAYERS_H
\ No newline at end of file
+#endif // AGON_LAYERS_H

From 0278b4ca10c0762fa98ff415cbe4f41b83d9ed8f Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Fri, 8 Nov 2024 18:36:54 +0000
Subject: [PATCH 5/6] Tidied comments, removed unused variables, disabled debug

---
 video/vdu_layers.h | 11 ++---------
 video/video.ino    |  2 +-
 2 files changed, 3 insertions(+), 10 deletions(-)

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
index defe9f5d..50cafd20 100644
--- a/video/vdu_layers.h
+++ b/video/vdu_layers.h
@@ -191,7 +191,6 @@ void VDUStreamProcessor::vdu_sys_layers(void) {
 		case VDP_LAYER_TILELAYER_UPDATE_LAYERBUFFER: {
 
 			// VDU 23,0,194,28,<tileLayerNum>
-			// Parameters are currently undefined but would facilitate drawing priority
 
 			uint8_t tileLayerNum = readByte_t();
 
@@ -203,7 +202,6 @@ void VDUStreamProcessor::vdu_sys_layers(void) {
 		case VDP_LAYER_TILELAYER_DRAW_LAYERBUFFER: {
 
 			// VDU 23,0,194,29,<tileLayerNum>
-			// Parameters are currently undefined but would facilitate drawing priority
 
 			uint8_t tileLayerNum = readByte_t();
 
@@ -214,7 +212,6 @@ void VDUStreamProcessor::vdu_sys_layers(void) {
 		case VDP_LAYER_TILELAYER_DRAW: {
 
 			// VDU 23,0,194,30,<tileLayerNum>
-			// Parameters are currently undefined but would facilitate drawing priority
 
 			uint8_t tileLayerNum = readByte_t();
 
@@ -781,12 +778,8 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_set_scroll(uint8_t tileLayerNu
 
 void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t tileLayerNum) {
 
-	int xPix = 0;		// X position in pixels is now always 0 as the offset is written directly to the tileRowBuffer
-	int yPix = 0;		// Y position in pixels is also always 0 as the displayed bitmap is the entire layer size.
-
 	uint8_t xPos;
 	uint8_t yPos;
-
 	uint8_t tileId;
 	uint8_t tileAttribute;
 	uint8_t tileLayerHeight;
@@ -797,7 +790,6 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 	uint8_t yOffset;
 	uint8_t tileMapWidth;
 	uint8_t tileMapHeight;
-	uint8_t tileRowOffset = 0;
 
 	switch (tileLayerNum) {
 
@@ -880,6 +872,7 @@ void VDUStreamProcessor::vdu_sys_layers_tilelayer_update_layerbuffer(uint8_t til
 
 					} break;
 				}
+
 			} else {				// Tile is normal and should be processed accordingly
 
 				// Check attribute to get tile draw direction and call appropriate draw function
@@ -1067,7 +1060,7 @@ void VDUStreamProcessor::writeTileToLayerBuffer(uint8_t tileId, uint8_t xPos, ui
 				destLineStartYOffset = tileLayerPixelWidth * y;
 				destPixelStart = destLineStart + destLineStartYOffset + destLineStartXOffset;
 				destPixelCount = 0;
-				
+
 				for (auto x=0; x<8; x++) {
 					sourcePixel = sourceTile + (y * 8) + x;
 					destPixel = destPixelStart + destPixelCount;
diff --git a/video/video.ino b/video/video.ino
index 5cac3c35..431843cb 100644
--- a/video/video.ino
+++ b/video/video.ino
@@ -50,7 +50,7 @@
 #include <WiFi.h>
 #include <fabgl.h>
 
-#define	DEBUG			1						// Serial Debug Mode: 1 = enable
+#define	DEBUG			0						// Serial Debug Mode: 1 = enable
 #define SERIALBAUDRATE	115200
 
 HardwareSerial	DBGSerial(0);

From 1852f1e2051b403525f78a7faf4d6d6b9377af04 Mon Sep 17 00:00:00 2001
From: Julian Regel <julian.regel@outlook.com>
Date: Fri, 8 Nov 2024 18:43:44 +0000
Subject: [PATCH 6/6] Updated Last Updated date

---
 video/vdu_layers.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/video/vdu_layers.h b/video/vdu_layers.h
index 50cafd20..d14c960e 100644
--- a/video/vdu_layers.h
+++ b/video/vdu_layers.h
@@ -4,7 +4,7 @@
 // Title:			Agon Tile Engine
 // Author:			Julian Regel
 // Created:			07/09/2024
-// Last Updated:	07/09/2024
+// Last Updated:	08/11/2024
 
 // This code included in VDP by adding:
 // #include "vdu_layers.h"