vmware-svga/examples/screen-cursor/main.c
Micah Dowty 68478eab4b There are many new tests and examples that we wrote internally at VMware,
but couldn't release immediately since they depended on virtual GPU features
which were not yet publicly released in any products.  This checkin moves those
features from our internal repository to the open source repository. Future   
development on these tests and examples will take place directly in the open
source repository.

The primary feature added by this patch is 'Screen Object', a new dynamic
display management extension supported by Workstation 7.0 and Fusion 3.0.
See the README for a quick explanation.
2009-10-21 20:20:49 +00:00

902 lines
21 KiB
C

/*
* 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_Define(&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;
}