2023-07-25 09:03:08 +02:00
|
|
|
#include "global.h"
|
|
|
|
#include "main.h"
|
|
|
|
#include "malloc.h"
|
|
|
|
#include "random.h"
|
|
|
|
#include "sprite.h"
|
2023-08-12 22:31:44 +02:00
|
|
|
#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;
|
|
|
|
}
|