camera: revise interface behavior to be more accurate

* there are two FIFO buffers (finally fixes Let's Golf)
* fix issues with camera start condition/cnt bit15
* add camera interface state to savestate
This commit is contained in:
Arisotura
2025-07-07 14:48:53 +02:00
parent 85d9202633
commit 7499958ad0
4 changed files with 112 additions and 53 deletions

View File

@ -282,9 +282,9 @@ void DSi::DoSavestateExtra(Savestate* file)
NDMAs[i].DoSavestate(file);
AES.DoSavestate(file);
CamModule.DoSavestate(file);
DSP.DoSavestate(file);
I2C.DoSavestate(file);
CamModule.DoSavestate(file);
SDMMC.DoSavestate(file);
SDIO.DoSavestate(file);

View File

@ -67,12 +67,15 @@ void DSi_CamModule::Reset()
CropStart = 0;
CropEnd = 0;
memset(DataBuffer, 0, 512*sizeof(u32));
BufferReadPos = 0;
BufferWritePos = 0;
Transferring = false;
memset(PixelBuffer, 0, sizeof(PixelBuffer));
CurPixelBuffer = 0;
BufferNumLines = 0;
CurCamera = nullptr;
// TODO: ideally this should be started when a camera is active
// instead of just being a constant thing
DSi.ScheduleEvent(Event_DSi_CamIRQ, false, kIRQInterval, 0, 0);
}
@ -89,9 +92,30 @@ void DSi_CamModule::DoSavestate(Savestate* file)
file->Var16(&ModuleCnt);
file->Var16(&Cnt);
/*file->VarArray(FrameBuffer, sizeof(FrameBuffer));
file->Var32(&TransferPos);
file->Var32(&FrameLength);*/
file->Var32(&CropStart);
file->Var32(&CropEnd);
file->Bool32(&Transferring);
file->VarArray(&PixelBuffer[0].Data, 512);
file->Var32(&PixelBuffer[0].ReadPos);
file->Var32(&PixelBuffer[0].WritePos);
file->VarArray(&PixelBuffer[1].Data, 512);
file->Var32(&PixelBuffer[1].ReadPos);
file->Var32(&PixelBuffer[1].WritePos);
file->Var8(&CurPixelBuffer);
file->Var32(&BufferNumLines);
if (!file->Saving)
{
DSi_Camera* activecam = nullptr;
if (Camera0->IsActivated()) activecam = Camera0;
else if (Camera1->IsActivated()) activecam = Camera1;
CurCamera = activecam;
}
}
@ -111,23 +135,30 @@ void DSi_CamModule::IRQ(u32 param)
if (Cnt & (1<<11))
DSi.SetIRQ(0, IRQ_DSi_Camera);
if (Cnt & (1<<15))
{
BufferReadPos = 0;
BufferWritePos = 0;
BufferNumLines = 0;
CurCamera = activecam;
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, kTransferStart, 0, 0);
}
}
DSi.ScheduleEvent(Event_DSi_CamIRQ, true, kIRQInterval, 0, 0);
}
void DSi_CamModule::TransferScanline(u32 line)
{
u32* dstbuf = &DataBuffer[BufferWritePos];
int maxlen = 512 - BufferWritePos;
if (line == 0)
{
if (!(Cnt & (1<<15)))
return;
BufferNumLines = 0;
Transferring = true;
}
if (Cnt & (1<<4))
return;
sPixelBuffer* buffer = &PixelBuffer[CurPixelBuffer];
u32* dstbuf = &buffer->Data[buffer->WritePos];
int maxlen = 512 - buffer->WritePos;
u32 tmpbuf[512];
int lines_next;
@ -138,6 +169,7 @@ void DSi_CamModule::TransferScanline(u32 line)
int copystart = 0;
int copylen = datalen;
bool line_last = false;
if (Cnt & (1<<14))
{
@ -146,7 +178,10 @@ void DSi_CamModule::TransferScanline(u32 line)
int ystart = (CropStart >> 16) & 0x1FF;
int yend = (CropEnd >> 16) & 0x1FF;
if (line < ystart || line > yend)
{
if (line == yend+1) line_last = true;
goto skip_line;
}
int xstart = (CropStart >> 1) & 0x1FF;
int xend = (CropEnd >> 1) & 0x1FF;
@ -163,7 +198,6 @@ void DSi_CamModule::TransferScanline(u32 line)
if (copylen > maxlen)
{
copylen = maxlen;
Cnt |= (1<<4);
}
if (Cnt & (1<<13))
@ -205,40 +239,63 @@ void DSi_CamModule::TransferScanline(u32 line)
memcpy(dstbuf, &tmpbuf[copystart], copylen*sizeof(u32));
}
buffer->WritePos += copylen;
if (buffer->WritePos > 512) buffer->WritePos = 512;
numscan = Cnt & 0x000F;
if (BufferNumLines >= numscan)
{
BufferReadPos = 0; // checkme
BufferWritePos = 0;
BufferNumLines = 0;
DSi.CheckNDMAs(0, 0x0B);
SwapPixelBuffers();
}
else
{
BufferWritePos += copylen;
if (BufferWritePos > 512) BufferWritePos = 512;
BufferNumLines++;
}
skip_line:
if (CurCamera->TransferDone())
bool done = CurCamera->TransferDone();
if (done || line_last)
{
// when the frame is finished, transfer any remaining data if needed
// (if the frame height isn't a multiple of the DMA interval)
if (BufferNumLines > 0)
{
BufferReadPos = 0;
BufferWritePos = 0;
BufferNumLines = 0;
DSi.CheckNDMAs(0, 0x0B);
SwapPixelBuffers();
}
}
if (done)
{
Transferring = false;
return;
}
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, delay, 0, line+1);
}
void DSi_CamModule::SwapPixelBuffers()
{
// pixel buffers are swapped every time a buffer is filled (ie. when the DMA interval is reached)
// the swap fails if the other buffer isn't empty
sPixelBuffer* otherbuf = &PixelBuffer[CurPixelBuffer ^ 1];
if (otherbuf->ReadPos < otherbuf->WritePos)
{
// overrun
Cnt |= (1<<4);
Transferring = false;
}
else
{
PixelBuffer[CurPixelBuffer].ReadPos = 0;
otherbuf->WritePos = 0;
CurPixelBuffer ^= 1;
DSi.CheckNDMAs(0, 0x0B);
}
}
u8 DSi_CamModule::Read8(u32 addr)
{
@ -253,7 +310,7 @@ u16 DSi_CamModule::Read16(u32 addr)
switch (addr)
{
case 0x04004200: return ModuleCnt;
case 0x04004202: return Cnt;
case 0x04004202: return Cnt | (Transferring ? 0x8000 : 0);
}
Log(LogLevel::Debug, "unknown DSi cam read16 %08X\n", addr);
@ -266,13 +323,12 @@ u32 DSi_CamModule::Read32(u32 addr)
{
case 0x04004204:
{
u32 ret = DataBuffer[BufferReadPos];
sPixelBuffer* buffer = &PixelBuffer[CurPixelBuffer ^ 1];
u32 ret = buffer->Data[buffer->ReadPos];
if (Cnt & (1<<15))
{
if (BufferReadPos < 511)
BufferReadPos++;
// CHECKME!!!!
// also presumably we should set bit4 in Cnt if there's no new data to be read
if (buffer->ReadPos < buffer->WritePos)
buffer->ReadPos++;
}
return ret;
@ -308,6 +364,7 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
// CHECKME
Cnt = 0;
Transferring = false;
}
if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5)))
@ -319,12 +376,9 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
case 0x04004202:
{
// TODO: during a transfer, clearing bit15 does not reflect immediately
// maybe it needs to finish the trasnfer or atleast the current block
// checkme
u16 oldmask;
if (Cnt & 0x8000)
if ((Cnt & 0x8000) || Transferring)
{
val &= 0x8F20;
oldmask = 0x601F;
@ -339,14 +393,8 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
if (val & (1<<5))
{
Cnt &= ~(1<<4);
BufferReadPos = 0;
BufferWritePos = 0;
}
if ((val & (1<<15)) && !(Cnt & (1<<15)))
{
// start transfer
//DSi::CheckNDMAs(0, 0x0B);
memset(PixelBuffer, 0, sizeof(PixelBuffer));
CurPixelBuffer = 0;
}
}
return;

View File

@ -118,15 +118,25 @@ private:
u32 CropStart, CropEnd;
// pixel data buffer holds a maximum of 512 words, regardless of how long scanlines are
u32 DataBuffer[512];
u32 BufferReadPos, BufferWritePos;
bool Transferring;
// pixel data buffers hold a maximum of 512 words, regardless of how long scanlines are
typedef struct
{
u32 Data[512];
u32 ReadPos, WritePos;
} sPixelBuffer;
sPixelBuffer PixelBuffer[2];
u8 CurPixelBuffer;
u32 BufferNumLines;
DSi_Camera* CurCamera;
static const u32 kIRQInterval;
static const u32 kScanlineTime;
static const u32 kTransferStart;
void SwapPixelBuffers();
};
}

View File

@ -1883,7 +1883,7 @@ void NDS::debug(u32 param)
//for (int i = 0; i < 9; i++)
// printf("VRAM %c: %02X\n", 'A'+i, GPU->VRAMCNT[i]);
Platform::FileHandle* shit = Platform::OpenFile("debug/pokeplat.bin", FileMode::Write);
/*Platform::FileHandle* shit = Platform::OpenFile("debug/pokeplat.bin", FileMode::Write);
Platform::FileWrite(ARM9.ITCM, 0x8000, 1, shit);
for (u32 i = 0x02000000; i < 0x02400000; i+=4)
{
@ -1900,20 +1900,21 @@ void NDS::debug(u32 param)
u32 val = NDS::ARM7Read32(i);
Platform::FileWrite(&val, 4, 1, shit);
}
Platform::CloseFile(shit);
Platform::CloseFile(shit);*/
/*FILE*
shit = fopen("debug/directboot9.bin", "wb");
shit = fopen("debug/camera9.bin", "wb");
fwrite(ARM9.ITCM, 0x8000, 1, shit);
for (u32 i = 0x02000000; i < 0x04000000; i+=4)
{
u32 val = DSi::ARM9Read32(i);
u32 val = ARM9Read32(i);
fwrite(&val, 4, 1, shit);
}
fclose(shit);
shit = fopen("debug/camera7.bin", "wb");
for (u32 i = 0x02000000; i < 0x04000000; i+=4)
{
u32 val = DSi::ARM7Read32(i);
u32 val = ARM7Read32(i);
fwrite(&val, 4, 1, shit);
}
fclose(shit);*/