|
Forum Index : Microcontroller and PC projects : C development in the modern world (routine for Volhout)
| Author | Message | ||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10583 |
In the Picomite release thread Volhout asked The key to this is a fast blit routine from one area of memory to another. So enter Claude stage left. I won't show the intermediate code steps to save space. Me: Claude: Me: Claude: Me: Claude: Me: Claude: I then take the code with test harness and paste into https://www.programiz.com/c-programming/online-compiler/ which runs the code passing all tests. Time to develop - 10 minutes #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> // Global framebuffer parameters #define HRES 640 #define VRES 480 #define FB_WORDS ((HRES * VRES + 4) / 5) uint32_t *sourceFB; uint32_t *destFB; uint32_t *frameBuf; int HRes = HRES; int VRes = VRES; // Helper function to set a pixel in RGB222 format void setPixel222(uint32_t *fb, int x, int y, uint8_t color) { if (x < 0 || x >= HRes || y < 0 || y >= VRes) return; int offset = y * HRes + x; int word_idx = offset / 5; int pixel_idx = offset % 5; int shift = pixel_idx * 6; uint32_t mask = ~(0x3F << shift); fb[word_idx] = (fb[word_idx] & mask) | ((color & 0x3F) << shift); } // Helper function to get a pixel in RGB222 format uint8_t getPixel222(uint32_t *fb, int x, int y) { if (x < 0 || x >= HRes || y < 0 || y >= VRes) return 0; int offset = y * HRes + x; int word_idx = offset / 5; int pixel_idx = offset % 5; int shift = pixel_idx * 6; return (fb[word_idx] >> shift) & 0x3F; } void blit222(int xsource, int ysource, int width, int height, int xdestination, int ydestination) { // Check if source and destination have the same alignment within a word if ((xsource % 5) == (xdestination % 5)) { // Optimized path - can use memcpy for aligned sections for (int y = 0; y < height; y++) { int src_y = ysource + y; int dst_y = ydestination + y; if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes) continue; int src_x = xsource; int dst_x = xdestination; int remaining = width; // Bounds check for entire row if (src_x < 0 || src_x + width > HRes || dst_x < 0 || dst_x + width > HRes) goto pixel_by_pixel; // Calculate starting positions int src_offset = src_y * HRes + src_x; int dst_offset = dst_y * HRes + dst_x; int src_word_idx = src_offset / 5; int src_pixel_idx = src_offset % 5; int dst_word_idx = dst_offset / 5; int dst_pixel_idx = dst_offset % 5; // Copy leading partial word pixel-by-pixel while (remaining > 0 && src_pixel_idx != 0) { uint32_t src_word = sourceFB[src_word_idx]; int src_shift = src_pixel_idx * 6; uint32_t pixel = (src_word >> src_shift) & 0x3F; int dst_shift = dst_pixel_idx * 6; uint32_t mask = ~(0x3F << dst_shift); destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift); src_pixel_idx++; dst_pixel_idx++; if (src_pixel_idx == 5) { src_pixel_idx = 0; src_word_idx++; } if (dst_pixel_idx == 5) { dst_pixel_idx = 0; dst_word_idx++; } remaining--; } // Copy complete words with memcpy int complete_words = remaining / 5; if (complete_words > 0) { memcpy(&destFB[dst_word_idx], &sourceFB[src_word_idx], complete_words * sizeof(uint32_t)); src_word_idx += complete_words; dst_word_idx += complete_words; remaining -= complete_words * 5; } // Copy trailing partial word pixel-by-pixel src_pixel_idx = 0; dst_pixel_idx = 0; while (remaining > 0) { uint32_t src_word = sourceFB[src_word_idx]; int src_shift = src_pixel_idx * 6; uint32_t pixel = (src_word >> src_shift) & 0x3F; int dst_shift = dst_pixel_idx * 6; uint32_t mask = ~(0x3F << dst_shift); destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift); src_pixel_idx++; dst_pixel_idx++; remaining--; } continue; pixel_by_pixel: // Fall back to pixel-by-pixel for this row for (int x = 0; x < width; x++) { int sx = xsource + x; int dx = xdestination + x; if (sx < 0 || sx >= HRes || dx < 0 || dx >= HRes) continue; int src_off = src_y * HRes + sx; int src_w_idx = src_off / 5; int src_p_idx = src_off % 5; uint32_t src_word = sourceFB[src_w_idx]; int src_shift = src_p_idx * 6; uint32_t pixel = (src_word >> src_shift) & 0x3F; int dst_off = dst_y * HRes + dx; int dst_w_idx = dst_off / 5; int dst_p_idx = dst_off % 5; int dst_shift = dst_p_idx * 6; uint32_t mask = ~(0x3F << dst_shift); destFB[dst_w_idx] = (destFB[dst_w_idx] & mask) | (pixel << dst_shift); } } } else { // Misaligned - must go pixel-by-pixel for (int y = 0; y < height; y++) { int src_y = ysource + y; int dst_y = ydestination + y; if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes) continue; for (int x = 0; x < width; x++) { int src_x = xsource + x; int dst_x = xdestination + x; if (src_x < 0 || src_x >= HRes || dst_x < 0 || dst_x >= HRes) continue; int src_offset = src_y * HRes + src_x; int src_word_idx = src_offset / 5; int src_pixel_idx = src_offset % 5; uint32_t src_word = sourceFB[src_word_idx]; int src_shift = src_pixel_idx * 6; uint32_t pixel = (src_word >> src_shift) & 0x3F; int dst_offset = dst_y * HRes + dst_x; int dst_word_idx = dst_offset / 5; int dst_pixel_idx = dst_offset % 5; int dst_shift = dst_pixel_idx * 6; uint32_t mask = ~(0x3F << dst_shift); destFB[dst_word_idx] = (destFB[dst_word_idx] & mask) | (pixel << dst_shift); } } } } void blit222_same(int xsource, int ysource, int width, int height, int xdestination, int ydestination) { // Check if regions overlap int overlap = 0; if (xdestination < xsource + width && xdestination + width > xsource && ydestination < ysource + height && ydestination + height > ysource) { overlap = 1; } // If no overlap, use the regular blit222 function if (!overlap) { // Temporarily set both source and dest to same buffer uint32_t *saved_src = sourceFB; uint32_t *saved_dst = destFB; sourceFB = frameBuf; destFB = frameBuf; blit222(xsource, ysource, width, height, xdestination, ydestination); sourceFB = saved_src; destFB = saved_dst; return; } // Overlapping case - need line buffer uint32_t lineBuffer[HRes / 5 + 1]; // Determine if we need to copy top-to-bottom or bottom-to-top int yincrement = 1; int ystart = 0; if (ydestination > ysource) { // Copying downward - start from bottom yincrement = -1; ystart = height - 1; } for (int y = ystart; y >= 0 && y < height; y += yincrement) { int src_y = ysource + y; int dst_y = ydestination + y; if (src_y < 0 || src_y >= VRes || dst_y < 0 || dst_y >= VRes) continue; // Copy source line to buffer for (int x = 0; x < width; x++) { int src_x = xsource + x; if (src_x < 0 || src_x >= HRes) continue; int src_offset = src_y * HRes + src_x; int src_word_idx = src_offset / 5; int src_pixel_idx = src_offset % 5; uint32_t src_word = frameBuf[src_word_idx]; int src_shift = src_pixel_idx * 6; uint32_t pixel = (src_word >> src_shift) & 0x3F; // Store in line buffer int buf_offset = x; int buf_word_idx = buf_offset / 5; int buf_pixel_idx = buf_offset % 5; int buf_shift = buf_pixel_idx * 6; if (buf_pixel_idx == 0) { lineBuffer[buf_word_idx] = 0; } lineBuffer[buf_word_idx] |= (pixel << buf_shift); } // Copy from buffer to destination line for (int x = 0; x < width; x++) { int dst_x = xdestination + x; if (dst_x < 0 || dst_x >= HRes) continue; // Extract from line buffer int buf_offset = x; int buf_word_idx = buf_offset / 5; int buf_pixel_idx = buf_offset % 5; int buf_shift = buf_pixel_idx * 6; uint32_t pixel = (lineBuffer[buf_word_idx] >> buf_shift) & 0x3F; // Write to destination int dst_offset = dst_y * HRes + dst_x; int dst_word_idx = dst_offset / 5; int dst_pixel_idx = dst_offset % 5; int dst_shift = dst_pixel_idx * 6; uint32_t mask = ~(0x3F << dst_shift); frameBuf[dst_word_idx] = (frameBuf[dst_word_idx] & mask) | (pixel << dst_shift); } } } // Test functions void printRegion(uint32_t *fb, int x, int y, int w, int h, const char *label) { printf("\n%s (at %d,%d size %dx%d):\n", label, x, y, w, h); for (int row = 0; row < h && row < 10; row++) { printf("Row %d: ", y + row); for (int col = 0; col < w && col < 20; col++) { printf("%02X ", getPixel222(fb, x + col, y + row)); } if (w > 20) printf("..."); printf("\n"); } if (h > 10) printf("...\n"); } int verifyBlit(uint32_t *src, uint32_t *dst, int xs, int ys, int w, int h, int xd, int yd) { int errors = 0; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { uint8_t src_pixel = getPixel222(src, xs + x, ys + y); uint8_t dst_pixel = getPixel222(dst, xd + x, yd + y); if (src_pixel != dst_pixel) { if (errors < 5) { printf("ERROR at dest(%d,%d): expected %02X, got %02X\n", xd + x, yd + y, src_pixel, dst_pixel); } errors++; } } } return errors; } void test_blit222() { printf("\n=== Testing blit222 (between different framebuffers) ===\n"); // Test 1: Aligned blit printf("\nTest 1: Aligned blit (both at x%%5 == 0)\n"); memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t)); memset(destFB, 0, FB_WORDS * sizeof(uint32_t)); // Create a test pattern for (int y = 10; y < 30; y++) { for (int x = 10; x < 50; x++) { setPixel222(sourceFB, x, y, (x + y) & 0x3F); } } blit222(10, 10, 40, 20, 100, 50); int errors = verifyBlit(sourceFB, destFB, 10, 10, 40, 20, 100, 50); printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); // Test 2: Misaligned blit printf("\nTest 2: Misaligned blit (x%%5 == 1 to x%%5 == 3)\n"); memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t)); memset(destFB, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 10; y < 30; y++) { for (int x = 11; x < 51; x++) { setPixel222(sourceFB, x, y, (x * 3 + y * 5) & 0x3F); } } blit222(11, 10, 40, 20, 103, 50); errors = verifyBlit(sourceFB, destFB, 11, 10, 40, 20, 103, 50); printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); // Test 3: Partial alignment with memcpy printf("\nTest 3: Aligned blit with leading/trailing pixels (x%%5 == 2)\n"); memset(sourceFB, 0, FB_WORDS * sizeof(uint32_t)); memset(destFB, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 5; y < 25; y++) { for (int x = 2; x < 47; x++) { setPixel222(sourceFB, x, y, ((x ^ y) * 7) & 0x3F); } } blit222(2, 5, 45, 20, 52, 100); errors = verifyBlit(sourceFB, destFB, 2, 5, 45, 20, 52, 100); printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); } void test_blit222_same() { printf("\n=== Testing blit222_same (within same framebuffer) ===\n"); // Test 1: Non-overlapping regions printf("\nTest 1: Non-overlapping regions\n"); memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 10; y < 30; y++) { for (int x = 10; x < 50; x++) { setPixel222(frameBuf, x, y, (x + y * 2) & 0x3F); } } // Save expected pattern uint8_t expected[40][20]; for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y); } } blit222_same(10, 10, 40, 20, 100, 100); int errors = 0; for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { if (getPixel222(frameBuf, 100 + x, 100 + y) != expected[x][y]) { errors++; } } } printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); // Test 2: Overlapping - shift right printf("\nTest 2: Overlapping regions - shift right\n"); memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 10; y < 30; y++) { for (int x = 10; x < 50; x++) { setPixel222(frameBuf, x, y, (x * 3 + y) & 0x3F); } } // Save expected pattern for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y); } } blit222_same(10, 10, 40, 20, 30, 10); // Shift right by 20 pixels errors = 0; for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { if (getPixel222(frameBuf, 30 + x, 10 + y) != expected[x][y]) { errors++; } } } printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); // Test 3: Overlapping - shift down printf("\nTest 3: Overlapping regions - shift down\n"); memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 10; y < 30; y++) { for (int x = 10; x < 50; x++) { setPixel222(frameBuf, x, y, (x + y * 5) & 0x3F); } } // Save expected pattern for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { expected[x][y] = getPixel222(frameBuf, 10 + x, 10 + y); } } blit222_same(10, 10, 40, 20, 10, 20); // Shift down by 10 pixels errors = 0; for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { if (getPixel222(frameBuf, 10 + x, 20 + y) != expected[x][y]) { errors++; } } } printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); // Test 4: Overlapping - shift up printf("\nTest 4: Overlapping regions - shift up\n"); memset(frameBuf, 0, FB_WORDS * sizeof(uint32_t)); for (int y = 20; y < 40; y++) { for (int x = 10; x < 50; x++) { setPixel222(frameBuf, x, y, (x * 2 + y * 3) & 0x3F); } } // Save expected pattern for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { expected[x][y] = getPixel222(frameBuf, 10 + x, 20 + y); } } blit222_same(10, 20, 40, 20, 10, 10); // Shift up by 10 pixels errors = 0; for (int y = 0; y < 20; y++) { for (int x = 0; x < 40; x++) { if (getPixel222(frameBuf, 10 + x, 10 + y) != expected[x][y]) { errors++; } } } printf("Result: %s (%d errors)\n", errors == 0 ? "PASS" : "FAIL", errors); } int main() { // Allocate framebuffers sourceFB = calloc(FB_WORDS, sizeof(uint32_t)); destFB = calloc(FB_WORDS, sizeof(uint32_t)); frameBuf = calloc(FB_WORDS, sizeof(uint32_t)); if (!sourceFB || !destFB || !frameBuf) { printf("Failed to allocate framebuffers\n"); return 1; } printf("Framebuffer test program\n"); printf("Resolution: %dx%d\n", HRES, VRES); printf("Framebuffer size: %d words (%d bytes)\n", FB_WORDS, FB_WORDS * 4); test_blit222(); test_blit222_same(); printf("\n=== All tests complete ===\n"); free(sourceFB); free(destFB); free(frameBuf); return 0; } Edited 2025-11-14 05:34 by matherp |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5464 |
Wauw... You 2 are a good team. Volhout PicomiteVGA PETSCII ROBOTS |
||||
| Mixtel90 Guru Joined: 05/10/2019 Location: United KingdomPosts: 8293 |
It's not just the AI, it's knowing what to ask and how to frame your question. Very impressive. :) Mick Zilog Inside! nascom.info for Nascom & Gemini Preliminary MMBasic docs & my PCB designs |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10583 |
Harm My current thinking is as follows: FLASH LOAD IMAGE flashslotno, filename$ [,format] This loads a RGB888 .BMP image into a flash slot in the format specified - default RGB121. The image can be as large as you can get in the flash slot and is not limited by MM.HRES/MM.VRES The firmware will note the physical size of the image in the flash slot= ImageHres,ImageVres then BLIT FLASH xflash, yflash, width, height, xframebuffer, yframebuffer [,transparentcolour] This will copy a sub section of the flash image into a framebuffer (not the physical screen except where that is also in RGB121). The user will of course need to know where in the flash image the elements they need are. However, by not restricting the flash image to be the size of the framebuffer, this gives great flexibility. So for example the image could be 1 sprite high and n sprites wide. Pixels in the optional transparent colour would not be copied To keep things simple the first implementation would be RGB121 only. Edited 2025-11-14 18:33 by matherp |
||||
| JohnS Guru Joined: 18/11/2011 Location: United KingdomPosts: 4142 |
A good team - indeed. John |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10583 |
The key is trying to be as precise as possible with the instruction: Me: Claude: Me: Claude: Code run on https://www.programiz.com/c-programming/online-compiler/ Result: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdbool.h> // Define framebuffer dimensions #define HResS 320 #define VResS 240 #define HResD 320 #define VResD 240 // ============================================================================ // BLIT121 FUNCTION // ================================================== void blit121(uint8_t *source, uint8_t *destination, int xsource, int ysource, int width, int height, int xdestination, int ydestination, int missingcolour) { // Check if we have a valid missing colour (transparency) int has_transparency = (missingcolour >= 0 && missingcolour <= 15); // Calculate source and destination row starts int src_offset_x = xsource >> 1; // xsource / 2 int dst_offset_x = xdestination >> 1; // xdestination / 2 // Check for alignment optimization opportunity int aligned = ((xsource & 1) == (xdestination & 1)) && !has_transparency; // Determine clipping boundaries for destination int start_x = 0; int start_y = 0; int end_x = width; int end_y = height; // Clip to destination framebuffer bounds if (xdestination < 0) { start_x = -xdestination; } if (ydestination < 0) { start_y = -ydestination; } if (xdestination + width > HResD) { end_x = HResD - xdestination; } if (ydestination + height > VResD) { end_y = VResD - ydestination; } // Nothing to draw if completely clipped if (start_x >= end_x || start_y >= end_y) { return; } int src_stride = (HResS + 1) >> 1; // bytes per row in source int dst_stride = (HResD + 1) >> 1; // bytes per row in destination if (aligned) { // Fast path: aligned copy with no transparency // Determine if first pixel is odd and last pixel is even int first_src_x = xsource + start_x; int last_src_x = xsource + end_x - 1; int first_pixel_odd = first_src_x & 1; int last_pixel_even = !(last_src_x & 1); // Calculate byte indices for the clipped region int first_byte = (first_src_x) >> 1; int last_byte = (last_src_x) >> 1; int first_dst_x = xdestination + start_x; int first_dst_byte = first_dst_x >> 1; for (int y = start_y; y < end_y; y++) { int src_row_base = (ysource + y) * src_stride; int dst_row_base = (ydestination + y) * dst_stride; uint8_t *src_row = source + src_row_base; uint8_t *dst_row = destination + dst_row_base; if (first_byte == last_byte) { // All pixels are in the same byte if (first_pixel_odd && last_pixel_even) { // Both nibbles dst_row[first_dst_byte] = src_row[first_byte]; } else if (first_pixel_odd) { // Only lower nibble dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0xF0) | (src_row[first_byte] & 0x0F); } else { // Only upper nibble dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0x0F) | (src_row[first_byte] & 0xF0); } } else { int byte_idx = 0; // Handle partial first byte if we start on an odd pixel if (first_pixel_odd) { dst_row[first_dst_byte] = (dst_row[first_dst_byte] & 0xF0) | (src_row[first_byte] & 0x0F); byte_idx = 1; } // Copy complete bytes int num_full_bytes = last_byte - first_byte + 1 - byte_idx - (last_pixel_even ? 1 : 0); for (int i = 0; i < num_full_bytes; i++) { dst_row[first_dst_byte + byte_idx + i] = src_row[first_byte + byte_idx + i]; } // Handle partial last byte if we end on an even pixel if (last_pixel_even) { int last_offset = last_byte - first_byte; dst_row[first_dst_byte + last_offset] = (dst_row[first_dst_byte + last_offset] & 0x0F) | (src_row[last_byte] & 0xF0); } } } } else { // Slow path: pixel-by-pixel copy for (int y = start_y; y < end_y; y++) { for (int x = start_x; x < end_x; x++) { int src_x = xsource + x; int src_y = ysource + y; int dst_x = xdestination + x; int dst_y = ydestination + y; // Get source pixel int src_byte_idx = src_y * src_stride + (src_x >> 1); uint8_t src_byte = source[src_byte_idx]; uint8_t src_pixel; if (src_x & 1) { src_pixel = src_byte & 0x0F; // Odd pixel (lower nibble) } else { src_pixel = (src_byte >> 4) & 0x0F; // Even pixel (upper nibble) } // Check transparency if (has_transparency && src_pixel == missingcolour) { continue; // Skip this pixel } // Write to destination int dst_byte_idx = dst_y * dst_stride + (dst_x >> 1); if (dst_x & 1) { // Odd pixel (lower nibble) destination[dst_byte_idx] = (destination[dst_byte_idx] & 0xF0) | src_pixel; } else { // Even pixel (upper nibble) destination[dst_byte_idx] = (destination[dst_byte_idx] & 0x0F) | (src_pixel << 4); } } } } } // ============================================================================ // TEST HARNESS // ============================================================================ // Helper functions for testing // Get a pixel from a framebuffer uint8_t get_pixel(uint8_t *fb, int x, int y, int hres) { int stride = (hres + 1) >> 1; int byte_idx = y * stride + (x >> 1); uint8_t byte_val = fb[byte_idx]; if (x & 1) { return byte_val & 0x0F; // Odd pixel } else { return (byte_val >> 4) & 0x0F; // Even pixel } } // Set a pixel in a framebuffer void set_pixel(uint8_t *fb, int x, int y, int hres, uint8_t color) { int stride = (hres + 1) >> 1; int byte_idx = y * stride + (x >> 1); if (x & 1) { fb[byte_idx] = (fb[byte_idx] & 0xF0) | (color & 0x0F); } else { fb[byte_idx] = (fb[byte_idx] & 0x0F) | ((color & 0x0F) << 4); } } // Fill a rectangle with a color void fill_rect(uint8_t *fb, int x, int y, int width, int height, int hres, int vres, uint8_t color) { for (int dy = 0; dy < height; dy++) { for (int dx = 0; dx < width; dx++) { if (x + dx >= 0 && x + dx < hres && y + dy >= 0 && y + dy < vres) { set_pixel(fb, x + dx, y + dy, hres, color); } } } } // Fill entire framebuffer with a color void clear_fb(uint8_t *fb, int hres, int vres, uint8_t color) { int size = ((hres + 1) >> 1) * vres; uint8_t fill_byte = (color << 4) | color; memset(fb, fill_byte, size); } // Create a pattern in framebuffer void create_pattern(uint8_t *fb, int hres, int vres) { for (int y = 0; y < vres; y++) { for (int x = 0; x < hres; x++) { uint8_t color = ((x / 8) + (y / 8)) & 0x0F; set_pixel(fb, x, y, hres, color); } } } // Print a small area of framebuffer (for debugging) void print_fb_area(uint8_t *fb, int x, int y, int width, int height, int hres, const char *label) { printf("%s:\n", label); for (int dy = 0; dy < height && dy < 16; dy++) { printf(" "); for (int dx = 0; dx < width && dx < 32; dx++) { if (x + dx >= 0 && x + dx < hres && y + dy >= 0) { printf("%X", get_pixel(fb, x + dx, y + dy, hres)); } else { printf("."); } } printf("\n"); } } // Compare two framebuffers in a region bool compare_region(uint8_t *fb1, uint8_t *fb2, int x, int y, int width, int height, int hres) { for (int dy = 0; dy < height; dy++) { for (int dx = 0; dx < width; dx++) { uint8_t p1 = get_pixel(fb1, x + dx, y + dy, hres); uint8_t p2 = get_pixel(fb2, x + dx, y + dy, hres); if (p1 != p2) { printf(" Mismatch at (%d,%d): expected %X, got %X\n", x + dx, y + dy, p1, p2); return false; } } } return true; } // Test result tracking typedef struct { int total; int passed; int failed; } TestResults; void init_results(TestResults *r) { r->total = 0; r->passed = 0; r->failed = 0; } void record_test(TestResults *r, bool passed, const char *test_name) { r->total++; if (passed) { r->passed++; printf(" ✓ PASS: %s\n", test_name); } else { r->failed++; printf(" ✗ FAIL: %s\n", test_name); } } // Allocate framebuffers uint8_t* alloc_fb(int hres, int vres) { int size = ((hres + 1) >> 1) * vres; return (uint8_t*)malloc(size); } // Test 1: Basic copy - no clipping, no transparency void test_basic_copy(TestResults *r) { printf("\n=== Test 1: Basic Copy ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); clear_fb(src, HResS, VResS, 5); clear_fb(dst, HResD, VResD, 10); clear_fb(expected, HResD, VResD, 10); fill_rect(src, 10, 10, 20, 15, HResS, VResS, 7); fill_rect(expected, 50, 50, 20, 15, HResD, VResD, 7); blit121(src, dst, 10, 10, 20, 15, 50, 50, -1); bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed, "Basic copy 20x15"); free(src); free(dst); free(expected); } // Test 2: Alignment cases void test_alignment(TestResults *r) { printf("\n=== Test 2: Alignment Cases ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); // Test 2a: Even to even (aligned) clear_fb(src, HResS, VResS, 3); clear_fb(dst, HResD, VResD, 8); clear_fb(expected, HResD, VResD, 8); fill_rect(src, 20, 20, 40, 30, HResS, VResS, 12); fill_rect(expected, 100, 100, 40, 30, HResD, VResD, 12); blit121(src, dst, 20, 20, 40, 30, 100, 100, -1); bool passed_2a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_2a, "Even to even (aligned)"); // Test 2b: Odd to odd (aligned) clear_fb(src, HResS, VResS, 3); clear_fb(dst, HResD, VResD, 8); clear_fb(expected, HResD, VResD, 8); fill_rect(src, 21, 21, 40, 30, HResS, VResS, 12); fill_rect(expected, 101, 101, 40, 30, HResD, VResD, 12); blit121(src, dst, 21, 21, 40, 30, 101, 101, -1); bool passed_2b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_2b, "Odd to odd (aligned)"); // Test 2c: Even to odd (misaligned) clear_fb(src, HResS, VResS, 3); clear_fb(dst, HResD, VResD, 8); clear_fb(expected, HResD, VResD, 8); fill_rect(src, 20, 20, 40, 30, HResS, VResS, 12); fill_rect(expected, 101, 101, 40, 30, HResD, VResD, 12); blit121(src, dst, 20, 20, 40, 30, 101, 101, -1); bool passed_2c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_2c, "Even to odd (misaligned)"); // Test 2d: Odd to even (misaligned) clear_fb(src, HResS, VResS, 3); clear_fb(dst, HResD, VResD, 8); clear_fb(expected, HResD, VResD, 8); fill_rect(src, 21, 21, 40, 30, HResS, VResS, 12); fill_rect(expected, 100, 100, 40, 30, HResD, VResD, 12); blit121(src, dst, 21, 21, 40, 30, 100, 100, -1); bool passed_2d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_2d, "Odd to even (misaligned)"); free(src); free(dst); free(expected); } // Test 3: Clipping tests void test_clipping(TestResults *r) { printf("\n=== Test 3: Clipping ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); // Test 3a: Clip left clear_fb(src, HResS, VResS, 2); clear_fb(dst, HResD, VResD, 9); clear_fb(expected, HResD, VResD, 9); fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6); fill_rect(expected, 0, 50, 20, 20, HResD, VResD, 6); blit121(src, dst, 10, 10, 30, 20, -10, 50, -1); bool passed_3a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_3a, "Clip left edge"); // Test 3b: Clip right clear_fb(src, HResS, VResS, 2); clear_fb(dst, HResD, VResD, 9); clear_fb(expected, HResD, VResD, 9); fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6); fill_rect(expected, HResD - 20, 50, 20, 20, HResD, VResD, 6); blit121(src, dst, 10, 10, 30, 20, HResD - 20, 50, -1); bool passed_3b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_3b, "Clip right edge"); // Test 3c: Clip top clear_fb(src, HResS, VResS, 2); clear_fb(dst, HResD, VResD, 9); clear_fb(expected, HResD, VResD, 9); fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6); fill_rect(expected, 50, 0, 30, 10, HResD, VResD, 6); blit121(src, dst, 10, 10, 30, 20, 50, -10, -1); bool passed_3c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_3c, "Clip top edge"); // Test 3d: Clip bottom clear_fb(src, HResS, VResS, 2); clear_fb(dst, HResD, VResD, 9); clear_fb(expected, HResD, VResD, 9); fill_rect(src, 10, 10, 30, 20, HResS, VResS, 6); fill_rect(expected, 50, VResD - 10, 30, 10, HResD, VResD, 6); blit121(src, dst, 10, 10, 30, 20, 50, VResD - 10, -1); bool passed_3d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_3d, "Clip bottom edge"); // Test 3e: Completely outside (should do nothing) clear_fb(dst, HResD, VResD, 9); clear_fb(expected, HResD, VResD, 9); blit121(src, dst, 10, 10, 30, 20, HResD + 100, 50, -1); bool passed_3e = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_3e, "Completely outside (no-op)"); free(src); free(dst); free(expected); } // Test 4: Transparency void test_transparency(TestResults *r) { printf("\n=== Test 4: Transparency ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); // Test 4a: Transparent color 0 clear_fb(src, HResS, VResS, 0); clear_fb(dst, HResD, VResD, 15); clear_fb(expected, HResD, VResD, 15); // Create a checkerboard pattern with color 0 and color 5 for (int y = 10; y < 30; y++) { for (int x = 10; x < 30; x++) { uint8_t color = ((x + y) & 1) ? 5 : 0; set_pixel(src, x, y, HResS, color); // Only color 5 should be copied if (color == 5) { set_pixel(expected, x + 40, y + 40, HResD, color); } } } blit121(src, dst, 10, 10, 20, 20, 50, 50, 0); bool passed_4a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_4a, "Transparent color 0"); // Test 4b: Transparent color 15 clear_fb(src, HResS, VResS, 15); clear_fb(dst, HResD, VResD, 3); clear_fb(expected, HResD, VResD, 3); for (int y = 10; y < 30; y++) { for (int x = 10; x < 30; x++) { uint8_t color = ((x + y) & 1) ? 7 : 15; set_pixel(src, x, y, HResS, color); if (color == 7) { set_pixel(expected, x + 40, y + 40, HResD, color); } } } blit121(src, dst, 10, 10, 20, 20, 50, 50, 15); bool passed_4b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_4b, "Transparent color 15"); // Test 4c: Mid-range transparent color clear_fb(src, HResS, VResS, 8); clear_fb(dst, HResD, VResD, 1); clear_fb(expected, HResD, VResD, 1); for (int y = 10; y < 30; y++) { for (int x = 10; x < 30; x++) { uint8_t color = ((x + y) & 1) ? 12 : 8; set_pixel(src, x, y, HResS, color); if (color == 12) { set_pixel(expected, x + 40, y + 40, HResD, color); } } } blit121(src, dst, 10, 10, 20, 20, 50, 50, 8); bool passed_4c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_4c, "Transparent color 8"); free(src); free(dst); free(expected); } // Test 5: Edge cases void test_edge_cases(TestResults *r) { printf("\n=== Test 5: Edge Cases ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); // Test 5a: Single pixel copy clear_fb(src, HResS, VResS, 0); clear_fb(dst, HResD, VResD, 0); clear_fb(expected, HResD, VResD, 0); set_pixel(src, 50, 50, HResS, 14); set_pixel(expected, 100, 100, HResD, 14); blit121(src, dst, 50, 50, 1, 1, 100, 100, -1); bool passed_5a = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_5a, "Single pixel copy"); // Test 5b: Single row clear_fb(src, HResS, VResS, 0); clear_fb(dst, HResD, VResD, 0); clear_fb(expected, HResD, VResD, 0); fill_rect(src, 10, 50, 50, 1, HResS, VResS, 9); fill_rect(expected, 20, 100, 50, 1, HResD, VResD, 9); blit121(src, dst, 10, 50, 50, 1, 20, 100, -1); bool passed_5b = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_5b, "Single row"); // Test 5c: Single column clear_fb(src, HResS, VResS, 0); clear_fb(dst, HResD, VResD, 0); clear_fb(expected, HResD, VResD, 0); fill_rect(src, 50, 10, 1, 50, HResS, VResS, 11); fill_rect(expected, 100, 20, 1, 50, HResD, VResD, 11); blit121(src, dst, 50, 10, 1, 50, 100, 20, -1); bool passed_5c = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_5c, "Single column"); // Test 5d: Odd width (tests partial byte handling) clear_fb(src, HResS, VResS, 1); clear_fb(dst, HResD, VResD, 1); clear_fb(expected, HResD, VResD, 1); fill_rect(src, 20, 20, 33, 25, HResS, VResS, 13); fill_rect(expected, 80, 80, 33, 25, HResD, VResD, 13); blit121(src, dst, 20, 20, 33, 25, 80, 80, -1); bool passed_5d = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed_5d, "Odd width (33 pixels)"); free(src); free(dst); free(expected); } // Test 6: Pattern preservation void test_pattern_preservation(TestResults *r) { printf("\n=== Test 6: Pattern Preservation ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); clear_fb(dst, HResD, VResD, 0); clear_fb(expected, HResD, VResD, 0); // Create a detailed pattern for (int y = 0; y < 40; y++) { for (int x = 0; x < 40; x++) { uint8_t color = (x ^ y) & 0x0F; set_pixel(src, x + 10, y + 10, HResS, color); set_pixel(expected, x + 100, y + 100, HResD, color); } } blit121(src, dst, 10, 10, 40, 40, 100, 100, -1); bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed, "XOR pattern preservation"); free(src); free(dst); free(expected); } // Test 7: Transparency with clipping void test_transparency_with_clipping(TestResults *r) { printf("\n=== Test 7: Transparency with Clipping ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); clear_fb(dst, HResD, VResD, 7); clear_fb(expected, HResD, VResD, 7); // Create checkerboard with transparent color for (int y = 0; y < 50; y++) { for (int x = 0; x < 50; x++) { uint8_t color = ((x + y) & 1) ? 4 : 0; set_pixel(src, x + 10, y + 10, HResS, color); } } // Simulate clipping: destination starts at -10, -10 // So only pixels from source (10,10) should be visible at dest (0,0) for (int y = 0; y < 40; y++) { for (int x = 0; x < 40; x++) { int src_x = x + 20; int src_y = y + 20; uint8_t color = ((src_x + src_y) & 1) ? 4 : 0; if (color != 0) { set_pixel(expected, x, y, HResD, color); } } } blit121(src, dst, 10, 10, 50, 50, -10, -10, 0); bool passed = compare_region(expected, dst, 0, 0, 50, 50, HResD); record_test(r, passed, "Transparency with clipping"); free(src); free(dst); free(expected); } // Test 8: All colors void test_all_colors(TestResults *r) { printf("\n=== Test 8: All Colors ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); clear_fb(dst, HResD, VResD, 0); clear_fb(expected, HResD, VResD, 0); // Create a pattern using all 16 colors for (int color = 0; color < 16; color++) { for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { set_pixel(src, 10 + color * 8 + x, 10 + y, HResS, color); set_pixel(expected, 50 + color * 8 + x, 50 + y, HResD, color); } } } blit121(src, dst, 10, 10, 128, 8, 50, 50, -1); bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed, "All 16 colors"); free(src); free(dst); free(expected); } // Test 9: Large area copy void test_large_area(TestResults *r) { printf("\n=== Test 9: Large Area Copy ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); clear_fb(dst, HResD, VResD, 2); clear_fb(expected, HResD, VResD, 2); create_pattern(src, HResS, VResS); // Copy a large area for (int y = 0; y < 150; y++) { for (int x = 0; x < 200; x++) { uint8_t color = get_pixel(src, x + 10, y + 10, HResS); set_pixel(expected, x + 50, y + 40, HResD, color); } } blit121(src, dst, 10, 10, 200, 150, 50, 40, -1); bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); record_test(r, passed, "Large area (200x150)"); free(src); free(dst); free(expected); } // Test 10: Boundary alignment tests void test_boundary_alignments(TestResults *r) { printf("\n=== Test 10: Boundary Alignments ===\n"); uint8_t *src = alloc_fb(HResS, VResS); uint8_t *dst = alloc_fb(HResD, VResD); uint8_t *expected = alloc_fb(HResD, VResD); // Test various byte boundary crossings int test_cases[][4] = { {0, 0, 17, 10}, // Start at even, odd width {1, 0, 17, 10}, // Start at odd, odd width {0, 0, 16, 10}, // Start at even, even width {1, 0, 16, 10}, // Start at odd, even width {7, 0, 18, 10}, // Start near byte boundary {15, 0, 18, 10}, // Start at odd byte boundary }; bool all_passed = true; for (int i = 0; i < 6; i++) { clear_fb(src, HResS, VResS, 1); clear_fb(dst, HResD, VResD, 1); clear_fb(expected, HResD, VResD, 1); int sx = test_cases[i][0]; int sy = test_cases[i][1]; int w = test_cases[i][2]; int h = test_cases[i][3]; fill_rect(src, sx, sy, w, h, HResS, VResS, 11); fill_rect(expected, 100, 100, w, h, HResD, VResD, 11); blit121(src, dst, sx, sy, w, h, 100, 100, -1); bool passed = compare_region(expected, dst, 0, 0, HResD, VResD, HResD); if (!passed) { printf(" Failed: sx=%d, sy=%d, w=%d, h=%d\n", sx, sy, w, h); all_passed = false; } } record_test(r, all_passed, "Boundary alignment variations"); free(src); free(dst); free(expected); } int main() { printf("========================================\n"); printf(" blit121 Comprehensive Test Suite\n"); printf("========================================\n"); printf("Source framebuffer: %dx%d\n", HResS, VResS); printf("Destination framebuffer: %dx%d\n", HResD, VResD); TestResults results; init_results(&results); test_basic_copy(&results); test_alignment(&results); test_clipping(&results); test_transparency(&results); test_edge_cases(&results); test_pattern_preservation(&results); test_transparency_with_clipping(&results); test_all_colors(&results); test_large_area(&results); test_boundary_alignments(&results); printf("\n========================================\n"); printf(" Test Results Summary\n"); printf("========================================\n"); printf("Total tests: %d\n", results.total); printf("Passed: %d ✓\n", results.passed); printf("Failed: %d ✗\n", results.failed); printf("Success rate: %.1f%%\n", (results.total > 0) ? (100.0 * results.passed / results.total) : 0.0); printf("========================================\n"); return (results.failed == 0) ? 0 : 1; } |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5464 |
Peter, BLIT FLASH also needs a parameter "flash_slot_no". Volhout PicomiteVGA PETSCII ROBOTS |
||||
| twofingers Guru Joined: 02/06/2014 Location: GermanyPosts: 1677 |
Slightly OT. @Peter: Just be careful when using Claude! https://en.wikipedia.org/wiki/Claude_(language_model) Kind regards Michael causality ≠ correlation ≠ coincidence |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |