pokeemerald/test/sprite.c

304 lines
8.5 KiB
C
Raw Normal View History

2023-07-25 09:03:08 +02:00
#include "global.h"
#include "main.h"
#include "malloc.h"
#include "random.h"
#include "sprite.h"
#include "test/test.h"
2023-07-25 09:03:08 +02:00
#define OAM_MATRIX_COUNT 32
EWRAM_DATA static u16 sSpritePriorities[MAX_SPRITES] = {0};
EWRAM_DATA static u8 sSpriteOrder[MAX_SPRITES] = {0};
static void Old_BuildOamBuffer(void);
static void ExpectEqOamBuffers(const struct OamData *oldOamBuffer, const struct OamData *newOamBuffer)
{
u32 i;
u32 matrices = 0;
// Compare the non-matrix data.
for (i = 0; i < gOamLimit; i++)
{
EXPECT(memcmp(&oldOamBuffer[i], &newOamBuffer[i], 6) == 0);
if (newOamBuffer[i].affineMode & ST_OAM_AFFINE_ON_MASK)
matrices |= 1 << newOamBuffer[i].matrixNum;
}
// Compare the matrix data.
for (i = 0; i < OAM_MATRIX_COUNT; i++)
{
if (matrices & (1 << i))
{
u32 base = 4 * i;
EXPECT_EQ(oldOamBuffer[base + 0].affineParam, newOamBuffer[base + 0].affineParam);
EXPECT_EQ(oldOamBuffer[base + 1].affineParam, newOamBuffer[base + 1].affineParam);
EXPECT_EQ(oldOamBuffer[base + 2].affineParam, newOamBuffer[base + 2].affineParam);
EXPECT_EQ(oldOamBuffer[base + 3].affineParam, newOamBuffer[base + 3].affineParam);
}
}
}
static void ResetSpriteData_(void)
{
u32 i;
ResetSpriteData();
for (i = 0; i < MAX_SPRITES; i++)
sSpriteOrder[i] = i;
}
static void BenchmarkBuildOamBuffer(bool32 preSort)
{
struct Benchmark oldBuildOamBuffer, newBuildOamBuffer;
struct OamData *oldOamBuffer = Alloc(sizeof(gMain.oamBuffer));
if (preSort)
Old_BuildOamBuffer();
BENCHMARK(&oldBuildOamBuffer)
{
Old_BuildOamBuffer();
}
memcpy(oldOamBuffer, gMain.oamBuffer, sizeof(gMain.oamBuffer));
if (preSort)
BuildOamBuffer();
BENCHMARK(&newBuildOamBuffer)
{
BuildOamBuffer();
}
ExpectEqOamBuffers(oldOamBuffer, gMain.oamBuffer);
EXPECT_FASTER(newBuildOamBuffer, oldBuildOamBuffer);
Free(oldOamBuffer);
}
TEST("BuildOamBuffer faster with no sprites")
{
ResetSpriteData_();
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster with max sprites (equal y/subpriority)")
{
u32 i;
ResetSpriteData_();
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, 0, 0);
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster with max sprites (random y/subpriority)")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
BenchmarkBuildOamBuffer(FALSE);
}
TEST("BuildOamBuffer faster on already-sorted max sprites")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES; i++)
CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
BenchmarkBuildOamBuffer(TRUE);
}
TEST("BuildOamBuffer faster with mix of sprites")
{
u32 i;
ResetSpriteData_();
SeedRng(0);
for (i = 0; i < MAX_SPRITES / 2; i++)
{
u32 spriteId = CreateSprite(&gDummySpriteTemplate, 0, Random() % 256, Random() % 256);
gSprites[spriteId].invisible = Random() % 4 == 0;
}
BenchmarkBuildOamBuffer(FALSE);
}
// Old implementation.
#define UBFIX
static void UpdateOamCoords(void)
{
u8 i;
for (i = 0; i < MAX_SPRITES; i++)
{
struct Sprite *sprite = &gSprites[i];
if (sprite->inUse && !sprite->invisible)
{
if (sprite->coordOffsetEnabled)
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX + gSpriteCoordOffsetX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY + gSpriteCoordOffsetY;
}
else
{
sprite->oam.x = sprite->x + sprite->x2 + sprite->centerToCornerVecX;
sprite->oam.y = sprite->y + sprite->y2 + sprite->centerToCornerVecY;
}
}
}
}
static void BuildSpritePriorities(void)
{
u16 i;
for (i = 0; i < MAX_SPRITES; i++)
{
struct Sprite *sprite = &gSprites[i];
u16 priority = sprite->subpriority | (sprite->oam.priority << 8);
sSpritePriorities[i] = priority;
}
}
static void SortSprites(void)
{
u8 i;
for (i = 1; i < MAX_SPRITES; i++)
{
u8 j = i;
struct Sprite *sprite1 = &gSprites[sSpriteOrder[i - 1]];
struct Sprite *sprite2 = &gSprites[sSpriteOrder[i]];
u16 sprite1Priority = sSpritePriorities[sSpriteOrder[i - 1]];
u16 sprite2Priority = sSpritePriorities[sSpriteOrder[i]];
s16 sprite1Y = sprite1->oam.y;
s16 sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
while (j > 0
&& ((sprite1Priority > sprite2Priority)
|| (sprite1Priority == sprite2Priority && sprite1Y < sprite2Y)))
{
u8 temp = sSpriteOrder[j];
sSpriteOrder[j] = sSpriteOrder[j - 1];
sSpriteOrder[j - 1] = temp;
// UB: If j equals 1, then j-- makes j equal 0.
// Then, sSpriteOrder[-1] gets accessed below.
// Although this doesn't result in a bug in the ROM,
// the behavior is undefined.
j--;
#ifdef UBFIX
if (j == 0)
break;
#endif
sprite1 = &gSprites[sSpriteOrder[j - 1]];
sprite2 = &gSprites[sSpriteOrder[j]];
sprite1Priority = sSpritePriorities[sSpriteOrder[j - 1]];
sprite2Priority = sSpritePriorities[sSpriteOrder[j]];
sprite1Y = sprite1->oam.y;
sprite2Y = sprite2->oam.y;
if (sprite1Y >= DISPLAY_HEIGHT)
sprite1Y = sprite1Y - 256;
if (sprite2Y >= DISPLAY_HEIGHT)
sprite2Y = sprite2Y - 256;
if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite1->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite1->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite1Y > 128)
sprite1Y = sprite1Y - 256;
}
}
if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
&& sprite2->oam.size == ST_OAM_SIZE_3)
{
u32 shape = sprite2->oam.shape;
if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
{
if (sprite2Y > 128)
sprite2Y = sprite2Y - 256;
}
}
}
}
}
static void CopyMatricesToOamBuffer(void)
{
u8 i;
for (i = 0; i < OAM_MATRIX_COUNT; i++)
{
u32 base = 4 * i;
gMain.oamBuffer[base + 0].affineParam = gOamMatrices[i].a;
gMain.oamBuffer[base + 1].affineParam = gOamMatrices[i].b;
gMain.oamBuffer[base + 2].affineParam = gOamMatrices[i].c;
gMain.oamBuffer[base + 3].affineParam = gOamMatrices[i].d;
}
}
static void AddSpritesToOamBuffer(void)
{
u8 i = 0;
u8 oamIndex = 0;
while (i < MAX_SPRITES)
{
struct Sprite *sprite = &gSprites[sSpriteOrder[i]];
if (sprite->inUse && !sprite->invisible && AddSpriteToOamBuffer(sprite, &oamIndex))
return;
i++;
}
while (oamIndex < gOamLimit)
{
gMain.oamBuffer[oamIndex] = gDummyOamData;
oamIndex++;
}
}
static void Old_BuildOamBuffer(void)
{
u8 temp;
UpdateOamCoords();
BuildSpritePriorities();
SortSprites();
temp = gMain.oamLoadDisabled;
gMain.oamLoadDisabled = TRUE;
AddSpritesToOamBuffer();
CopyMatricesToOamBuffer();
gMain.oamLoadDisabled = temp;
//sShouldProcessSpriteCopyRequests = TRUE;
}