/* * This is a test app for virtual hardware cursor overlays. It * provides a number of test cases which exercise and demonstrate the * various cursor modes. * * This example requires SVGA Screen Object support. (The cursor * functionality we're testing has nothing to do with Screen Object, * but Screen Object is required by our text drawing code...) */ #include "svga.h" #include "gmr.h" #include "screen.h" #include "intr.h" #include "screendraw.h" #include "keyboard.h" #include "vmbackdoor.h" #include "timer.h" #include "mt19937ar.h" #include "math.h" /* * Constants */ #define GMRID_SCREEN_DRAW 0 #define GMRID_NOISE 1 #define FRAME_RATE 60 /* * Global data */ int currentTest = -1; const int testListX = 150; const int testListY = 70; const int testListItemHeight = 22; SVGAScreenObject myScreen = { .structSize = sizeof(SVGAScreenObject), .id = 0, .flags = SVGA_SCREEN_HAS_ROOT | SVGA_SCREEN_IS_PRIMARY, .size = { 800, 600 }, .root = { 150000, -0x20000000 }, }; /* * Test Cases */ void testAlphaArrow(void) { static const SVGAFifoCmdDefineAlphaCursor cursor = { .id = 0, .hotspotX = 1, .hotspotY = 1, .width = 36, .height = 51, }; static const uint32 data[] = { # include "rgba_arrow.h" }; void *fifoData; /* * Switch to 8-bit mode. Alpha cursors should work even if the * legacy framebuffer is in a low color depth. */ SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 8); SVGA_BeginDefineAlphaCursor(&cursor, &fifoData); memcpy(fifoData, data, sizeof data); SVGA_FIFOCommitAll(); } void testGradient(int size) { const SVGAFifoCmdDefineAlphaCursor cursor = { .id = 0, .hotspotX = size / 2, .hotspotY = size / 2, .width = size, .height = size, }; uint32 *data; int x, y; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 8); SVGA_BeginDefineAlphaCursor(&cursor, (void**) &data); for (y = 0; y < cursor.height; y++) { for (x = 0; x < cursor.width; x++) { uint8 alpha = y * 255 / cursor.height; /* Solid white, with pre-multiplied alpha: L = 255 * alpha / 255 */ uint8 luma = alpha; *(data++) = (alpha << 24) | (luma << 16) | (luma << 8) | luma; } } SVGA_FIFOCommitAll(); } void testGradient64(void) { testGradient(64); } void testGradient180(void) { testGradient(180); } void testGradient256(void) { testGradient(256); } void testMonochrome(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 24, .width = 48, .height = 48, .andMaskDepth = 1, .xorMaskDepth = 1, }; static const uint8 data[] = { # include "beachball_mono.h" }; void *andData, *xorData; /* * Switch to 32bpp mode, just because we can. Monochrome cursors * work in any framebuffer depth. */ SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineCursor(&cursor, &andData, &xorData); memcpy(andData, data, sizeof data); SVGA_FIFOCommitAll(); } void testMonochromeXOR(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 24, .width = 48, .height = 48, .andMaskDepth = 1, .xorMaskDepth = 1, }; static const uint8 data[] = { # include "beachball_mono_xor.h" }; void *andData, *xorData; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineCursor(&cursor, &andData, &xorData); memcpy(andData, data, sizeof data); SVGA_FIFOCommitAll(); } void testMonochromeLarge(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 50, .hotspotY = 50, .width = 100, .height = 98, .andMaskDepth = 1, .xorMaskDepth = 1, }; static const uint8 data[] = { # include "chip_mono.h" }; void *andData, *xorData; /* * Switch to 32bpp mode, just because we can. Monochrome cursors * work in any framebuffer depth. */ SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineCursor(&cursor, &andData, &xorData); memcpy(andData, data, sizeof data); SVGA_FIFOCommitAll(); } void testANDXOR32(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 16, .hotspotY = 16, .width = 32, .height = 32, .andMaskDepth = 32, .xorMaskDepth = 32 }; uint32 *andData, *xorData; int x, y; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineCursor(&cursor, (void**) &andData, (void**) &xorData); for (y = 0; y < cursor.height; y++) { for (x = 0; x < cursor.width; x++) { *(andData++) = 0x808080; *(xorData++) = y * 127 / cursor.height; } } SVGA_FIFOCommitAll(); } static const uint32 yellowCrabData[] = { # include "yellow_crab_rgba.h" }; void buildCrabANDMask(uint8 *andData) { const uint32 *rgba = yellowCrabData; const int width = 48; const int height = 50; uint32 andPitch = ((width + 31) / 32) * 4; uint8 *andLine; int x, y; for (y = 0; y < height; y++) { andLine = andData; andData += andPitch; memset(andLine, 0, andPitch); for (x = 0; x < width; x++) { uint32 color = *(rgba++); *andLine <<= 1; if ((color & 0xFF000000) == 0) { /* * Transparent pixel */ *andLine |= 1; } if ((x & 7) == 7) { andLine++; } } } } void testCrabAlpha(void) { static const SVGAFifoCmdDefineAlphaCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 25, .width = 48, .height = 50, }; void *fifoData; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineAlphaCursor(&cursor, &fifoData); memcpy(fifoData, yellowCrabData, sizeof yellowCrabData); SVGA_FIFOCommitAll(); } void testCrabANDXOR32(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 25, .width = 48, .height = 50, .andMaskDepth = 1, .xorMaskDepth = 32 }; uint8 *andData; uint32 *xorData; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 32); SVGA_BeginDefineCursor(&cursor, (void**) &andData, (void**) &xorData); buildCrabANDMask(andData); memcpy(xorData, yellowCrabData, sizeof yellowCrabData); SVGA_FIFOCommitAll(); } void testCrabANDXOR16(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 25, .width = 48, .height = 50, .andMaskDepth = 1, .xorMaskDepth = 16 }; const uint32 *rgba = yellowCrabData; uint8 *andData; uint16 *xorData; int x, y; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 16); if (SVGA_ReadReg(SVGA_REG_DEPTH) != 16) { Console_Panic("Expected SVGA_REG_DEPTH == 16 for 16bpp mode"); } SVGA_BeginDefineCursor(&cursor, (void**) &andData, (void**) &xorData); buildCrabANDMask(andData); for (y = 0; y < cursor.height; y++) { for (x = 0; x < cursor.width; x++) { uint32 color = *(rgba++); /* * Convert to RGB 5-6-5 */ uint8 r = (color >> 19) & 0x1F; uint8 g = (color >> 10) & 0x3F; uint8 b = (color >> 3) & 0x1F; *(xorData++) = (r << 11) | (g << 5) | b; } } SVGA_FIFOCommitAll(); } void testCrabANDXOR8(void) { static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 25, .width = 48, .height = 50, .andMaskDepth = 1, .xorMaskDepth = 8 }; static const uint8 crabPixels[] = { # include "yellow_crab_256_pixels.h" }; static const uint8 crabColormap[] = { # include "yellow_crab_256_colormap.h" }; uint8 *andData, *xorData; int i; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 8); if (SVGA_ReadReg(SVGA_REG_PSEUDOCOLOR) != TRUE) { Console_Panic("Expected SVGA_REG_PSUEDOCOLOR == TRUE for 8bpp mode"); } /* * Load the crab's colormap into the SVGA palette registers. */ for (i = 0; i < arraysize(crabColormap); i++) { SVGA_WriteReg(SVGA_PALETTE_BASE + i, crabColormap[i]); } SVGA_BeginDefineCursor(&cursor, (void**) &andData, (void**) &xorData); buildCrabANDMask(andData); memcpy(xorData, crabPixels, sizeof crabPixels); SVGA_FIFOCommitAll(); } void createPaletteCursor(void) { /* * Set up a cursor which shows every color in the palette. It has * a 1-pixel border of color 255, but every other color forms a * 16x16 grid in which each square is a different color. Each grid * square is 3x3 pixels. * * We also take this opportunity to use an 8-bit AND mask, but it's * fully opaque (all zeroes). * * This does mean that to function correctly, this cursor needs * color 0 to be black. The SVGA device doesn't specify whether * AND/XOR masks are applied before or after pseudocolor emulation, * so we need to be okay with either. */ static const SVGAFifoCmdDefineCursor cursor = { .id = 0, .hotspotX = 24, .hotspotY = 24, .width = 49, .height = 49, .andMaskDepth = 8, .xorMaskDepth = 8 }; uint8 *andData, *xorData; uint8 *line; int x, y; uint32 pitch = roundup(cursor.width, sizeof(uint32)) * sizeof(uint32); SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 8); if (SVGA_ReadReg(SVGA_REG_PSEUDOCOLOR) != TRUE) { Console_Panic("Expected SVGA_REG_PSUEDOCOLOR == TRUE for 8bpp mode"); } SVGA_BeginDefineCursor(&cursor, (void**) &andData, (void**) &xorData); memset(andData, 0, pitch * cursor.height); for (y = 0; y < cursor.height; y++) { line = xorData; xorData += pitch; for (x = 0; x < cursor.width; x++) { uint8 color; if (y == 0 || x == 0 || y == cursor.height - 1 || x == cursor.width - 1) { /* Border color */ color = 0xFF; } else { int row = (y - 1) / 3; int column = (x - 1) / 3; color = row * 16 + column; } *(line++) = color; } } SVGA_FIFOCommitAll(); } void animatePalette(void) { /* * Animate the palette. This is stolen from the vbe-palette test... */ static int tick = 0; int i; /* * Let the phase of each color channel slowly drift around. */ const float rPhase = tick * 0.001; const float gPhase = tick * 0.002; const float bPhase = tick * 0.003; /* * Animate every color except 0 (used for the AND mask) and 255 * (for the border). */ for (i = 1; i < 255; i++) { const int x = (i & 0x0F) - 3; const int y = (i >> 4) - 3; const float t = (x*x + y*y) * 0.05 + tick * 0.02; const uint8 r = sinf(t + rPhase) * 0x7f + 0x80; const uint8 g = sinf(t + gPhase) * 0x7f + 0x80; const uint8 b = sinf(t + bPhase) * 0x7f + 0x80; SVGA_WriteReg(SVGA_PALETTE_BASE + i * 3 + 0, r); SVGA_WriteReg(SVGA_PALETTE_BASE + i * 3 + 1, g); SVGA_WriteReg(SVGA_PALETTE_BASE + i * 3 + 2, b); } tick++; } void blit32(const uint32 *src, int srcX, int srcY, int srcWidth, uint32 *dest, int destX, int destY, int destWidth, int copyWidth, int copyHeight) { src += srcX + srcY * srcWidth; dest += destX + destY * destWidth; while (copyHeight--) { memcpy32(dest, src, copyWidth); src += srcWidth; dest += destWidth; } } void testCursorAnim(void) { /* * Animate a cursor image of a moon orbiting a planet. The cursor * hotspot is always centered on the planet, but we dynamically * size the cursor according to the bounding box around the planet * and moon, and we dynamically adjust the hotspot to keep it * centered on the moon. */ const int moonWidth = 10; const int planetWidth = 20; static int tick = 0; float angle = tick * 0.03; tick++; const int orbitRadius = 40; int orbitX = cosf(angle) * orbitRadius; int orbitY = sinf(angle) * orbitRadius; int width, height, planetX, planetY, moonX, moonY; if (orbitX >= 0) { width = planetWidth + orbitX; planetX = planetWidth/2; moonX = planetX + orbitX; } else { width = planetWidth - orbitX; moonX = planetWidth/2; planetX = moonX - orbitX; } if (orbitY >= 0) { height = planetWidth + orbitY; planetY = planetWidth/2; moonY = planetY + orbitY; } else { height = planetWidth - orbitY; moonY = planetWidth/2; planetY = moonY - orbitY; } const SVGAFifoCmdDefineAlphaCursor cursor = { .id = 0, .hotspotX = planetX, .hotspotY = planetY, .width = width, .height = height, }; uint32 *image; static const uint32 planet[] = { # include "planet_rgba.h" }; static const uint32 moon[] = { # include "moon_rgba.h" }; SVGA_WriteReg(SVGA_REG_BITS_PER_PIXEL, 8); SVGA_BeginDefineAlphaCursor(&cursor, (void**) &image); memset32(image, 0, width * height); blit32(planet, 0, 0, planetWidth, image, planetX - planetWidth/2, planetY - planetWidth/2, width, planetWidth, planetWidth); blit32(moon, 0, 0, moonWidth, image, moonX - moonWidth/2, moonY - moonWidth/2, width, moonWidth, moonWidth); SVGA_FIFOCommitAll(); } /* * gTestCases -- * * Master array of cursor test cases. Each one has a title and a * function pointer. The function is run once when a test mode is * selected. */ struct { void (*fn)(void); const char *title; void (*animateFn)(void); } gTestCases[] = { { testAlphaArrow, "Translucent arrow cursor (36x51)" }, { testGradient64, "Gradient from transparent white to opaque white (64x64)" }, { testGradient180, "Gradient from transparent white to opaque white (180x180)" }, { testGradient256, "Gradient from transparent white to opaque white (256x256)" }, { testMonochrome, "Monochrome beachball cursor (48x48)" }, { testMonochromeXOR, "Monochrome beachball cursor with XOR pixels (48x48)" }, { testMonochromeLarge, "Monochrome chip cursor (100x96)" }, { testANDXOR32, "AND masks off 7 LSBs, XOR draws blue gradient (32x32)" }, { testCrabAlpha, "Yellow crab, alpha blended (48x50)" }, { testCrabANDXOR32, "Yellow crab, 1-bit AND, 32-bit XOR (48x50)" }, { testCrabANDXOR16, "Yellow crab, 1-bit AND, 16-bit XOR (48x50)" }, { testCrabANDXOR8, "Yellow crab, 1-bit AND, 8-bit XOR (48x50)" }, { createPaletteCursor, "Palette animation, 8-bit AND/XOR (49x49)", animatePalette }, { testCursorAnim, "Animated cursor (variable size and hotspot)", testCursorAnim } }; /* * selectTest -- * * Switch to a new current test case. Hilight the new test, un-hilight * the old one, and call the new test's function. */ void selectTest(int newTest) { if (newTest < 0) { newTest += arraysize(gTestCases); } else if (newTest >= arraysize(gTestCases)) { newTest -= arraysize(gTestCases); } if (currentTest != newTest) { if (currentTest >= 0) { ScreenDraw_Border(testListX, testListY + testListItemHeight * currentTest, myScreen.size.width - testListX, testListY + testListItemHeight * (currentTest + 1), 0x000000, 2); } currentTest = newTest; ScreenDraw_Border(testListX, testListY + testListItemHeight * currentTest, myScreen.size.width - testListX, testListY + testListItemHeight * (currentTest + 1), 0xFFFF00, 2); gTestCases[newTest].fn(); } } /* * allocNoise -- * * Allocates a new GMR, and fills it with random noise. */ static void allocNoise(void) { const uint32 numPages = 500; const uint32 numWords = numPages * PAGE_SIZE / sizeof(uint32); PPN pages = GMR_DefineContiguous(GMRID_NOISE, numPages); uint32 *ptr = PPN_POINTER(pages); int i; init_genrand(0); for (i = 0; i < numWords; i++) { ptr[i] = genrand_int32(); } } /* * prepareNoiseRect -- * * Prepare some noise as the source for a blit. * This defines the GMRFB, and generates a random source origin. */ static void prepareNoiseRect(SVGASignedPoint *origin) // OUT { const uint32 bytesPerLine = 512; static const SVGAGMRImageFormat format = {{{ 32, 24 }}}; const SVGAGuestPtr gPtr = { GMRID_NOISE, 0 }; const uint32 rand = genrand_int32(); Screen_DefineGMRFB(gPtr, bytesPerLine, format); origin->x = rand & 0x7F; origin->y = (rand >> 8) & 0x7F; } /* * main -- * * Initialization and main loop. This reads a global array of test * cases, and presents a menu which cycles through them. The main * loop services keyboard and mouse input, and draws an animated test * pattern for testing cursor interaction with the base layer. */ int main(void) { Intr_Init(); Intr_SetFaultHandlers(SVGA_DefaultFaultHandler); Timer_InitPIT(PIT_HZ / FRAME_RATE); Intr_SetMask(PIT_IRQ, TRUE); SVGA_Init(); GMR_Init(); Keyboard_Init(); VMBackdoor_MouseInit(TRUE); Heap_Reset(); SVGA_SetMode(0, 0, 32); Screen_Init(); ScreenDraw_Init(GMRID_SCREEN_DRAW); allocNoise(); Screen_Create(&myScreen); /* * Draw the menu of test modes. */ ScreenDraw_SetScreen(myScreen.id, myScreen.size.width, myScreen.size.height); Console_Clear(); ScreenDraw_Border(0, 0, myScreen.size.width, myScreen.size.height, 0x808080, 1); Console_WriteString("Cursor tests:\n" "Select with up/down arrows. " "Move cursor with mouse or WASD keys.\n"); for (currentTest = 0; currentTest < arraysize(gTestCases); currentTest++) { Console_MoveTo(testListX + 2, testListY + testListItemHeight * currentTest + 2); Console_Format("%d. %s", currentTest + 1, gTestCases[currentTest].title); } /* * Draw a white square on the right side of the menu, so we can see * what the cursors look like on a solid white background. */ ScreenDraw_Rectangle(myScreen.size.width - testListX + 10, testListY, myScreen.size.width - 10, myScreen.size.height - 10, 0xFFFFFF); /* * Main loop. */ selectTest(0); while (1) { int prevTest = currentTest - 1; int nextTest = currentTest + 1; static VMMousePacket mouseState; const int kbdMouseSpeed = 100; Bool needCursorUpdate = FALSE; while (Keyboard_IsKeyPressed(KEY_UP)) { selectTest(prevTest); } while (Keyboard_IsKeyPressed(KEY_DOWN)) { selectTest(nextTest); } while (VMBackdoor_MouseGetPacket(&mouseState)) { needCursorUpdate = TRUE; } if (Keyboard_IsKeyPressed('w')) { mouseState.y -= kbdMouseSpeed; needCursorUpdate = TRUE; } if (Keyboard_IsKeyPressed('s')) { mouseState.y += kbdMouseSpeed; needCursorUpdate = TRUE; } if (Keyboard_IsKeyPressed('a')) { mouseState.x -= kbdMouseSpeed; needCursorUpdate = TRUE; } if (Keyboard_IsKeyPressed('d')) { mouseState.x += kbdMouseSpeed; needCursorUpdate = TRUE; } if (needCursorUpdate) { /* * Send a cursor position update. In order to test the SVGA * device's cursor screen ID support, we alternate between * sending cursor updates in virtual coordinates and in * screen-relative coordinates. * * If all is well, cursor movement should still be smooth. If * there's a bug in decoding, the cursor will jitter as it's * moved, or the cursor may flicker if it's moved over an * animated region like the static bar on the left side of * the screen. */ /* * Fixed-point to pixels. */ SVGASignedPoint pixelLocation = { mouseState.x * myScreen.size.width / 65535, mouseState.y * myScreen.size.height / 65535 }; uint32 screenId; static Bool toggle; if (toggle) { screenId = SVGA_ID_INVALID; pixelLocation.x += myScreen.root.x; pixelLocation.y += myScreen.root.y; toggle = FALSE; } else { screenId = myScreen.id; toggle = TRUE; } SVGA_MoveCursor(TRUE, pixelLocation.x, pixelLocation.y, screenId); } /* * Draw some noise on the side of the screen, for testing overlay compositing. */ SVGASignedPoint srcOrigin; SVGASignedRect noiseRect = { 10, testListY, testListX - 10, myScreen.size.height - 10, }; prepareNoiseRect(&srcOrigin); Screen_BlitFromGMRFB(&srcOrigin, &noiseRect, myScreen.id); /* * Some tests are animated... */ if (gTestCases[currentTest].animateFn) { gTestCases[currentTest].animateFn(); } /* * Wait for the next frame. */ Intr_Halt(); } return 0; }