579 lines
16 KiB
C
579 lines
16 KiB
C
|
/*
|
||
|
* SVGA3D example: Test harness and low-level example program for
|
||
|
* Guest Memory Regions.
|
||
|
*
|
||
|
* With Guest Memory regions, the SVGA device can perform DMA
|
||
|
* operations directly between guest system memory and host
|
||
|
* VRAM. Guest drivers use the device's GMR registers to set up
|
||
|
* regions of guest memory which can be accessed by the device, then
|
||
|
* the driver refers to these regions by ID when sending pointers over
|
||
|
* the command FIFO.
|
||
|
*
|
||
|
* GMRs support physically contiguous or discontiguous memory. This
|
||
|
* example is a bit contrived because we're testing GMRs without an
|
||
|
* operating system or a virtual memory subsystem- in a real OS,
|
||
|
* support for physically discontiguous addresses would often be
|
||
|
* required in order to ensure that the GMR's address space matches
|
||
|
* that of a particular virtual address space in the OS. In this
|
||
|
* example, we just test physically discontiguous regions for the sake
|
||
|
* of testing them.
|
||
|
*
|
||
|
* This test harness is focused on system memory GMRs, however it also
|
||
|
* ends up testing much of the GLSurface and GLFBO code, since it
|
||
|
* performs GMR-to-GMR copies by way of surface DMA operations.
|
||
|
*
|
||
|
* Copyright (C) 2008-2009 VMware, Inc. Licensed under the MIT
|
||
|
* License, please see the README.txt. All rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include "svga.h"
|
||
|
#include "svga3dutil.h"
|
||
|
#include "svga3dtext.h"
|
||
|
#include "console_vga.h"
|
||
|
#include "gmr.h"
|
||
|
#include "math.h"
|
||
|
#include "mt19937ar.h"
|
||
|
|
||
|
/* Maximum number of copy boxes we'll test with. The host has no limit. */
|
||
|
#define MAX_COPY_BOXES 128
|
||
|
|
||
|
/*
|
||
|
* Global data
|
||
|
*/
|
||
|
|
||
|
static uint32 tempSurfaceId;
|
||
|
static uint32 randSeed;
|
||
|
static uint32 testIters;
|
||
|
static uint32 testRegionSize;
|
||
|
static const char *testPass;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* TestPattern_Write --
|
||
|
* TestPattern_Check --
|
||
|
*
|
||
|
* Write/check an arbitrary deterministic test pattern in the
|
||
|
* provided buffer. The buffer must be a multiple of 4 bytes long.
|
||
|
*
|
||
|
* Instead of generating a unique random number for every word,
|
||
|
* which would be pretty slow, this generates a prime number of
|
||
|
* random words, which then repeat across the entire check range.
|
||
|
*/
|
||
|
|
||
|
#define PATTERN_BUFFER_LEN 41 // Must be prime
|
||
|
|
||
|
void
|
||
|
TestPattern_Write(uint32 *buffer,
|
||
|
uint32 size)
|
||
|
{
|
||
|
#ifndef DISABLE_CHECKING
|
||
|
uint32 pattern[PATTERN_BUFFER_LEN];
|
||
|
int i;
|
||
|
|
||
|
init_genrand(randSeed);
|
||
|
for (i = 0; i < PATTERN_BUFFER_LEN; i++) {
|
||
|
pattern[i] = genrand_int32();
|
||
|
}
|
||
|
i = 0;
|
||
|
|
||
|
size /= sizeof *buffer;
|
||
|
while (size--) {
|
||
|
*(buffer++) = pattern[i];
|
||
|
if (++i == PATTERN_BUFFER_LEN) {
|
||
|
i = 0;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void
|
||
|
TestPattern_Check(uint32 *buffer,
|
||
|
uint32 size,
|
||
|
uint32 offset,
|
||
|
uint32 line,
|
||
|
uint32 index)
|
||
|
{
|
||
|
#ifndef DISABLE_CHECKING
|
||
|
uint32 pattern[PATTERN_BUFFER_LEN];
|
||
|
int i;
|
||
|
|
||
|
init_genrand(randSeed);
|
||
|
for (i = 0; i < PATTERN_BUFFER_LEN; i++) {
|
||
|
pattern[i] = genrand_int32();
|
||
|
}
|
||
|
|
||
|
offset /= sizeof *buffer;
|
||
|
size /= sizeof *buffer;
|
||
|
|
||
|
i = offset % PATTERN_BUFFER_LEN;
|
||
|
|
||
|
while (size) {
|
||
|
uint32 v = pattern[i];
|
||
|
if (++i == PATTERN_BUFFER_LEN) {
|
||
|
i = 0;
|
||
|
}
|
||
|
|
||
|
if (*buffer != v) {
|
||
|
SVGA_Disable();
|
||
|
ConsoleVGA_Init();
|
||
|
Console_Format("Test pattern mismatch on %4x.%4x\n"
|
||
|
"Test pass: %s\n"
|
||
|
"Mismatch at %08x, with %08x bytes left in block.\n\n",
|
||
|
line, index, testPass, buffer, size * sizeof *buffer);
|
||
|
|
||
|
size = MIN(size, 16);
|
||
|
while (size) {
|
||
|
Console_Format("Actual: %08x Expected: %08x\n",
|
||
|
*buffer, v);
|
||
|
buffer++;
|
||
|
size--;
|
||
|
|
||
|
v = pattern[i];
|
||
|
if (++i == PATTERN_BUFFER_LEN) {
|
||
|
i = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Intr_Disable();
|
||
|
Intr_Halt();
|
||
|
}
|
||
|
|
||
|
buffer++;
|
||
|
size--;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* GMR_GenericCopy --
|
||
|
*
|
||
|
* Copy between two GMRs, using an arbitrarily shaped buffer
|
||
|
* surface and an arbitrary list of copy boxes.
|
||
|
*
|
||
|
* In the copy boxes, the 'source' represents locations
|
||
|
* on both guest surfaces and the 'destination' represents
|
||
|
* a locations in host VRAM.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
GMR_GenericCopy(SVGAGuestPtr *dest,
|
||
|
SVGAGuestPtr *src,
|
||
|
SVGA3dSize *surfSize,
|
||
|
SVGA3dSurfaceFormat format,
|
||
|
SVGA3dCopyBox *boxes,
|
||
|
uint32 numBoxes)
|
||
|
{
|
||
|
SVGA3dSize *mipSizes;
|
||
|
SVGA3dSurfaceFace *faces;
|
||
|
SVGA3dCopyBox *dmaBoxes;
|
||
|
SVGA3dGuestImage srcImage = { *src };
|
||
|
SVGA3dGuestImage destImage = { *dest };
|
||
|
SVGA3dSurfaceImageId hostImage = { tempSurfaceId };
|
||
|
|
||
|
SVGA3D_BeginDefineSurface(tempSurfaceId, 0, format, &faces, &mipSizes, 1);
|
||
|
faces[0].numMipLevels = 1;
|
||
|
mipSizes[0] = *surfSize;
|
||
|
SVGA_FIFOCommitAll();
|
||
|
|
||
|
SVGA3D_BeginSurfaceDMA(&srcImage, &hostImage, SVGA3D_WRITE_HOST_VRAM,
|
||
|
&dmaBoxes, numBoxes);
|
||
|
memcpy(dmaBoxes, boxes, numBoxes * sizeof boxes[0]);
|
||
|
SVGA_FIFOCommitAll();
|
||
|
|
||
|
SVGA3D_BeginSurfaceDMA(&destImage, &hostImage, SVGA3D_READ_HOST_VRAM,
|
||
|
&dmaBoxes, numBoxes);
|
||
|
memcpy(dmaBoxes, boxes, numBoxes * sizeof boxes[0]);
|
||
|
SVGA_FIFOCommitAll();
|
||
|
|
||
|
SVGA3D_DestroySurface(tempSurfaceId);
|
||
|
|
||
|
/* Wait for both DMA operations to finish. */
|
||
|
SVGA_SyncToFence(SVGA_InsertFence());
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Display_BeginPass --
|
||
|
*
|
||
|
* Begin a new test pass, and update the on-screen display.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
Display_BeginPass(const char *pass)
|
||
|
{
|
||
|
testPass = pass;
|
||
|
|
||
|
Console_Clear();
|
||
|
Console_Format("VMware SVGA3D Example:\n"
|
||
|
"Guest Memory Region stress-test.\n"
|
||
|
"\n"
|
||
|
"Host capabilities\n"
|
||
|
"-----------------\n"
|
||
|
"\n"
|
||
|
" Max IDs: %d\n"
|
||
|
" Max Descriptor Len: %d\n"
|
||
|
"\n"
|
||
|
"Test status\n"
|
||
|
"-----------\n"
|
||
|
"\n"
|
||
|
" Iterations: %d\n"
|
||
|
" Seed: %08x\n"
|
||
|
" Running: %s\n"
|
||
|
"\n"
|
||
|
#ifdef DISABLE_CHECKING
|
||
|
"CHECKING DISABLED. This test can't fail.\n",
|
||
|
#else
|
||
|
"Test is running successfully so far. Will Panic on failure.\n",
|
||
|
#endif
|
||
|
gGMR.maxIds, gGMR.maxDescriptorLen, testIters,
|
||
|
randSeed, testPass);
|
||
|
|
||
|
VMBackdoor_VGAScreenshot();
|
||
|
SVGA3DText_Update();
|
||
|
SVGA3DUtil_ClearFullscreen(CID, SVGA3D_CLEAR_COLOR, 0x000080, 1.0f, 0);
|
||
|
SVGA3DText_Draw();
|
||
|
SVGA3DUtil_PresentFullscreen();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* runTestPass --
|
||
|
*
|
||
|
* Run one test pass- create two large GMRs, one contiguous and one
|
||
|
* discontiguous. Copy a test pattern back and forth between the
|
||
|
* two buffers, using the provided surface size and type.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
runTestPass(uint32 testRegionSize,
|
||
|
SVGA3dSize *surfSize,
|
||
|
SVGA3dSurfaceFormat format,
|
||
|
SVGA3dCopyBox *boxes,
|
||
|
uint32 numBoxes)
|
||
|
{
|
||
|
SVGAGuestPtr contig = { 0, 0 };
|
||
|
SVGAGuestPtr evenPages = { gGMR.maxIds - 1, 0 };
|
||
|
int i;
|
||
|
|
||
|
uint32 contigPages = GMR_DefineContiguous(contig.gmrId, gGMR.maxDescriptorLen * 2);
|
||
|
uint32 discontigPages = GMR_DefineEvenPages(evenPages.gmrId, gGMR.maxDescriptorLen);
|
||
|
|
||
|
/*
|
||
|
* Write a test pattern into the contiguous GMR.
|
||
|
*/
|
||
|
|
||
|
TestPattern_Write(PPN_POINTER(contigPages), testRegionSize);
|
||
|
TestPattern_Check(PPN_POINTER(contigPages), testRegionSize, 0, __LINE__, 0);
|
||
|
|
||
|
/*
|
||
|
* Copy from contiguous to discontiguous.
|
||
|
*/
|
||
|
|
||
|
GMR_GenericCopy(&evenPages, &contig, surfSize, format, boxes, numBoxes);
|
||
|
|
||
|
/*
|
||
|
* Check the discontiguous GMR, page-by-page.
|
||
|
*/
|
||
|
|
||
|
for (i = 0; i < testRegionSize / PAGE_SIZE; i++) {
|
||
|
TestPattern_Check(PPN_POINTER(discontigPages + 2*i),
|
||
|
PAGE_SIZE, PAGE_SIZE * i, __LINE__, i);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Clear the contiguous GMR, then copy data back into it from the discontiguous GMR.
|
||
|
*/
|
||
|
|
||
|
memset(PPN_POINTER(contigPages), 0x42, testRegionSize);
|
||
|
GMR_GenericCopy(&contig, &evenPages, surfSize, format, boxes, numBoxes);
|
||
|
|
||
|
/*
|
||
|
* Check the contiguous GMR again.
|
||
|
*/
|
||
|
|
||
|
TestPattern_Check(PPN_POINTER(contigPages), testRegionSize, 0, __LINE__, i);
|
||
|
|
||
|
GMR_FreeAll();
|
||
|
Heap_Reset();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* createBoxes --
|
||
|
*
|
||
|
* Create an array of N copyboxes which cover an entire surface.
|
||
|
* This begins with a single large copybox, and iteratively splits
|
||
|
* small boxes off from a random face on the original box.
|
||
|
*
|
||
|
* This function can and will generate degenerate copy boxes
|
||
|
* (zero-size). The SVGA3D device must ignore those boxes.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
createBoxes(SVGA3dSize *size,
|
||
|
SVGA3dCopyBox *boxes,
|
||
|
uint32 numBoxes)
|
||
|
{
|
||
|
uint32 i;
|
||
|
SVGA3dCopyBox space = {
|
||
|
.w = size->width,
|
||
|
.h = size->height,
|
||
|
.d = size->depth,
|
||
|
};
|
||
|
|
||
|
init_genrand(randSeed);
|
||
|
|
||
|
for (i = 0; i < numBoxes - 1; i++) {
|
||
|
uint32 rand = genrand_int32();
|
||
|
uint32 a;
|
||
|
memcpy(&boxes[i], &space, sizeof space);
|
||
|
switch (rand % 6) {
|
||
|
|
||
|
case 0: /* X- */
|
||
|
a = rand % space.w;
|
||
|
boxes[i].w = a;
|
||
|
space.x += a;
|
||
|
space.w -= a;
|
||
|
break;
|
||
|
|
||
|
case 1: /* Y- */
|
||
|
a = rand % space.h;
|
||
|
boxes[i].h = a;
|
||
|
space.y += a;
|
||
|
space.h -= a;
|
||
|
break;
|
||
|
|
||
|
case 2: /* Z- */
|
||
|
a = rand % space.d;
|
||
|
boxes[i].d = a;
|
||
|
space.z += a;
|
||
|
space.d -= a;
|
||
|
break;
|
||
|
|
||
|
case 3: /* X+ */
|
||
|
a = rand % space.w;
|
||
|
boxes[i].w = a;
|
||
|
space.w -= a;
|
||
|
boxes[i].x += space.w;
|
||
|
break;
|
||
|
|
||
|
case 4: /* Y+ */
|
||
|
a = rand % space.h;
|
||
|
boxes[i].h = a;
|
||
|
space.h -= a;
|
||
|
boxes[i].y += space.h;
|
||
|
break;
|
||
|
|
||
|
case 5: /* Z+ */
|
||
|
a = rand % space.d;
|
||
|
boxes[i].d = a;
|
||
|
space.d -= a;
|
||
|
boxes[i].z += space.d;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
boxes[i] = space;
|
||
|
|
||
|
for (i = 0; i < numBoxes; i++) {
|
||
|
boxes[i].srcx = boxes[i].x;
|
||
|
boxes[i].srcy = boxes[i].y;
|
||
|
boxes[i].srcz = boxes[i].z;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* createMisaligned1dBoxes --
|
||
|
*
|
||
|
* Create an array of N 1-dimensional copyboxes, most of which
|
||
|
* have a width of PAGE_SIZE-1 bytes.
|
||
|
*
|
||
|
* The boxes may extend past the end of 'size'. This is okay,
|
||
|
* the SVGA3D device is responsible for clipping them.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
createMisaligned1dBoxes(uint32 size,
|
||
|
SVGA3dCopyBox *boxes,
|
||
|
uint32 numBoxes)
|
||
|
{
|
||
|
uint32 offset = 0;
|
||
|
uint32 i;
|
||
|
|
||
|
memset(boxes, 0, sizeof *boxes * numBoxes);
|
||
|
|
||
|
for (i = 0; i < numBoxes - 1; i++) {
|
||
|
boxes[i].x = boxes[i].srcx = offset;
|
||
|
boxes[i].w = PAGE_SIZE-1;
|
||
|
boxes[i].h = 1;
|
||
|
boxes[i].d = 1;
|
||
|
offset += boxes[i].w;
|
||
|
}
|
||
|
|
||
|
boxes[i].x = boxes[i].srcx = offset;
|
||
|
boxes[i].w = size - offset;
|
||
|
boxes[i].h = 1;
|
||
|
boxes[i].d = 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* runTests --
|
||
|
*
|
||
|
* Main function to run one iteration of all tests.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
runTests(void)
|
||
|
{
|
||
|
/* Maximum size of worst-case-discontiguous region we can represent */
|
||
|
uint32 largeRegionSize = gGMR.maxDescriptorLen * PAGE_SIZE;
|
||
|
|
||
|
/* Smaller region, to speed up other testing. */
|
||
|
uint32 regionSize = 0x20 * PAGE_SIZE;
|
||
|
|
||
|
/* Smallest region, suitable for 1D textures. */
|
||
|
uint32 tinyRegionSize = 1024;
|
||
|
|
||
|
SVGA3dSize size1dLarge = {
|
||
|
.width = largeRegionSize,
|
||
|
.height = 1,
|
||
|
.depth = 1,
|
||
|
};
|
||
|
|
||
|
SVGA3dSize size1d = {
|
||
|
.width = tinyRegionSize,
|
||
|
.height = 1,
|
||
|
.depth = 1,
|
||
|
};
|
||
|
|
||
|
SVGA3dSize size2d = {
|
||
|
.width = 0x100,
|
||
|
.height = regionSize / 0x100,
|
||
|
.depth = 1,
|
||
|
};
|
||
|
|
||
|
SVGA3dSize size3d = {
|
||
|
.width = 0x40,
|
||
|
.height = 0x40,
|
||
|
.depth = regionSize / 0x1000,
|
||
|
};
|
||
|
|
||
|
/* A single maximally-sized 1D copybox. The host will clip it. */
|
||
|
SVGA3dCopyBox maxBox1d = {
|
||
|
.w = 0xFFFFFFFFUL,
|
||
|
.h = 1,
|
||
|
.d = 1,
|
||
|
};
|
||
|
|
||
|
SVGA3dCopyBox boxes[MAX_COPY_BOXES];
|
||
|
|
||
|
/*
|
||
|
* Basic per-surface-format tests.
|
||
|
*
|
||
|
* Note that 3D compressed textures are not expected to work yet,
|
||
|
* so we skip those tests.
|
||
|
*/
|
||
|
|
||
|
#define TEST_FORMAT_2D(f, b) \
|
||
|
{ \
|
||
|
Display_BeginPass("Single copy via 1D " #f " surface."); \
|
||
|
runTestPass(tinyRegionSize*b, &size1d, SVGA3D_ ## f, &maxBox1d, 1); \
|
||
|
\
|
||
|
Display_BeginPass("Single copy via 2D " #f " surface."); \
|
||
|
createBoxes(&size2d, boxes, 1); \
|
||
|
runTestPass(regionSize*b, &size2d, SVGA3D_ ## f, boxes, 1); \
|
||
|
}
|
||
|
|
||
|
#define TEST_FORMAT(f, b) \
|
||
|
{ \
|
||
|
TEST_FORMAT_2D(f, b) \
|
||
|
\
|
||
|
Display_BeginPass("Single copy via 3D " #f " surface."); \
|
||
|
createBoxes(&size3d, boxes, 1); \
|
||
|
runTestPass(regionSize*b, &size3d, SVGA3D_ ## f, boxes, 1); \
|
||
|
}
|
||
|
|
||
|
TEST_FORMAT(BUFFER, 1) // Buffers use their own host VRAM type
|
||
|
TEST_FORMAT(LUMINANCE8, 1) // Test a simple 8bpp format
|
||
|
TEST_FORMAT(ALPHA8, 1) // To isolate alpha channel bugs
|
||
|
TEST_FORMAT(A8R8G8B8, 4) // ARGB surfaces have more readback paths than others
|
||
|
TEST_FORMAT_2D(DXT2, 1) // Test 4x4 block size, and compressed texture upload/download
|
||
|
|
||
|
#undef TEST_FORMAT
|
||
|
#undef TEST_FORMAT_2D
|
||
|
|
||
|
/*
|
||
|
* Test large buffers (Limited by max size of worst-case fragmented GMR)
|
||
|
*/
|
||
|
|
||
|
Display_BeginPass("Single copy via 1D BUFFER surface. (Large region)");
|
||
|
runTestPass(largeRegionSize, &size1dLarge, SVGA3D_BUFFER, &maxBox1d, 1);
|
||
|
|
||
|
/*
|
||
|
* Test with randomly subdivided copyboxes.
|
||
|
*/
|
||
|
|
||
|
#define TEST_FORMAT_2D(f, b) \
|
||
|
{ \
|
||
|
Display_BeginPass("Subdivided copy via 2D " #f " surface."); \
|
||
|
createBoxes(&size2d, boxes, MAX_COPY_BOXES); \
|
||
|
runTestPass(regionSize*b, &size2d, SVGA3D_ ## f, boxes, MAX_COPY_BOXES); \
|
||
|
}
|
||
|
|
||
|
#define TEST_FORMAT(f, b) \
|
||
|
{ \
|
||
|
TEST_FORMAT_2D(f, b) \
|
||
|
\
|
||
|
Display_BeginPass("Subdivided copy via 3D " #f " surface."); \
|
||
|
createBoxes(&size3d, boxes, MAX_COPY_BOXES); \
|
||
|
runTestPass(regionSize*b, &size3d, SVGA3D_ ## f, boxes, MAX_COPY_BOXES); \
|
||
|
}
|
||
|
|
||
|
TEST_FORMAT(BUFFER, 1)
|
||
|
TEST_FORMAT(ALPHA8, 1)
|
||
|
TEST_FORMAT(A8R8G8B8, 4)
|
||
|
TEST_FORMAT_2D(DXT2, 1) // Test compressed texture rectangle clipping
|
||
|
|
||
|
#undef TEST_FORMAT
|
||
|
#undef TEST_FORMAT_2D
|
||
|
|
||
|
/*
|
||
|
* Test another large 1D copy, split into slightly misaligned chunks.
|
||
|
*/
|
||
|
|
||
|
Display_BeginPass("Misaligned copies via 1D BUFFER surface. (Large region)");
|
||
|
createMisaligned1dBoxes(largeRegionSize, boxes, MAX_COPY_BOXES);
|
||
|
runTestPass(largeRegionSize, &size1dLarge, SVGA3D_BUFFER, boxes, MAX_COPY_BOXES);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* main --
|
||
|
*
|
||
|
* Entry point and main loop for the example.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
main(void)
|
||
|
{
|
||
|
SVGA3DUtil_InitFullscreen(CID, 640, 480);
|
||
|
SVGA3DText_Init();
|
||
|
GMR_Init();
|
||
|
Heap_Reset();
|
||
|
|
||
|
tempSurfaceId = SVGA3DUtil_AllocSurfaceID();
|
||
|
testRegionSize = gGMR.maxDescriptorLen * PAGE_SIZE;
|
||
|
|
||
|
while (1) {
|
||
|
runTests();
|
||
|
|
||
|
randSeed = genrand_int32();
|
||
|
testIters++;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|