Add support for 25 light panels (SDK side).
This commit is contained in:
parent
55443cccb8
commit
3d5be3017b
@ -69,7 +69,8 @@ SMX_API uint16_t SMX_GetInputState(int pad);
|
||||
SMX_API void SMX_SetLights(const char lightData[864]);
|
||||
|
||||
// This is the same as SMX_SetLights, but specifies the size of the buffer. The
|
||||
// buffer size must be 864 bytes (2 pads * 9 panels * 16 lights * 3).
|
||||
// buffer size must be either 864 bytes (2 pads * 9 panels * 16 lights * 3) or
|
||||
// 1350 bytes (2 pads * 9 panels * 25 lights * 3).
|
||||
SMX_API void SMX_SetLights2(const char *lightData, int lightDataSize);
|
||||
|
||||
// By default, the panels light automatically when stepped on. If a lights command is sent by
|
||||
|
@ -70,17 +70,25 @@ SMX_API void SMX_SetLights(const char lightData[864])
|
||||
}
|
||||
SMX_API void SMX_SetLights2(const char *lightData, int lightDataSize)
|
||||
{
|
||||
// The lightData into data per pad.
|
||||
// The lightData into data per pad depending on whether we've been
|
||||
// given 16 or 25 lights of data.
|
||||
string lights[2];
|
||||
const int BytesPerPad16 = 9*16*3;
|
||||
const int BytesPerPad25 = 9*25*3;
|
||||
if(lightDataSize == 2*BytesPerPad16)
|
||||
{
|
||||
lights[0] = string(lightData, BytesPerPad16);
|
||||
lights[1] = string(lightData + BytesPerPad16, BytesPerPad16);
|
||||
}
|
||||
else if(lightDataSize == 2*BytesPerPad25)
|
||||
{
|
||||
lights[0] = string(lightData, BytesPerPad25);
|
||||
lights[1] = string(lightData + BytesPerPad25, BytesPerPad25);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(ssprintf("SMX_SetLights2: lightDataSize is invalid (must be %ii)\n", 2*BytesPerPad16));
|
||||
Log(ssprintf("SMX_SetLights2: lightDataSize is invalid (must be %i or %i)\n",
|
||||
2*BytesPerPad16, 2*BytesPerPad25));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,12 @@ bool SMX::SMXDevice::IsPlayer2Locked() const
|
||||
bool SMX::SMXDevice::GetConfig(SMXConfig &configOut)
|
||||
{
|
||||
LockMutex Lock(m_Lock);
|
||||
return GetConfigLocked(configOut);
|
||||
}
|
||||
|
||||
bool SMX::SMXDevice::GetConfigLocked(SMXConfig &configOut)
|
||||
{
|
||||
m_Lock.AssertLockedByCurrentThread();
|
||||
|
||||
// If SetConfig was called to write a new configuration but we haven't sent it
|
||||
// yet, return it instead of the configuration we read alst, so GetConfig
|
||||
|
@ -54,6 +54,7 @@ public:
|
||||
// Get the configuration of the connected device (or the most recently read configuration if
|
||||
// we're not connected).
|
||||
bool GetConfig(SMXConfig &configOut);
|
||||
bool GetConfigLocked(SMXConfig &configOut);
|
||||
|
||||
// Set the configuration of the connected device.
|
||||
//
|
||||
|
@ -265,29 +265,59 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
|
||||
// 89AB 89AB 89AB
|
||||
// CDEF CDEF CDEF
|
||||
//
|
||||
// Set sLightsCommand[iPad][0] to include 0123 4567, and [1] to 89AB CDEF.
|
||||
string sLightCommands[2][2]; // sLightCommands[command][pad]
|
||||
// If we're on a 25-light device, we have an additional grid of 3x3 LEDs:
|
||||
//
|
||||
//
|
||||
// x x x x
|
||||
// 0 1 2
|
||||
// x x x x
|
||||
// 3 4 5
|
||||
// x x x x
|
||||
// 6 7 8
|
||||
// x x x x
|
||||
//
|
||||
// Set sLightsCommand[iPad][0] to include 0123 4567, [1] to 89AB CDEF,
|
||||
// and [2] to the 3x3 grid.
|
||||
string sLightCommands[3][2]; // sLightCommands[command][pad]
|
||||
|
||||
// Read the linearly arranged color data we've been given and split it into top and
|
||||
// bottom commands for each pad.
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
// If there's no data for this pad, leave the command empty.
|
||||
const string &sLightsDataForPad = sPanelLights[iPad];
|
||||
string sLightsDataForPad = sPanelLights[iPad];
|
||||
if(sLightsDataForPad.empty())
|
||||
continue;
|
||||
|
||||
// Sanity check the lights data. It should have 9*4*4*3 bytes of data: RGB for each of 4x4
|
||||
// LEDs on 9 panels.
|
||||
if(sPanelLights[iPad].size() != 9*16*3)
|
||||
// Sanity check the lights data. For 4x4 lights, it should have 9*4*4*3 bytes of
|
||||
// data: RGB for each of 4x4 LEDs on 9 panels. For 25-light panels there should
|
||||
// be 4x4+3x3 (25) lights of data.
|
||||
int LightSize4x4 = 9*4*4*3;
|
||||
int LightSize25 = 9*5*5*3;
|
||||
if(sLightsDataForPad.size() != LightSize4x4 && sLightsDataForPad.size() != LightSize25)
|
||||
{
|
||||
Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 3*3*16*3, sPanelLights[iPad].size()));
|
||||
Log(ssprintf("SetLights: Lights data should be %i or %i bytes, received %i",
|
||||
LightSize4x4, LightSize25, sLightsDataForPad.size()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// The 2 command sends the top 4x2 lights, and 3 sends the bottom 4x2.
|
||||
sLightCommands[0][iPad] = "2";
|
||||
sLightCommands[1][iPad] = "3";
|
||||
// If we've been given 16 lights, pad to 25.
|
||||
if(sLightsDataForPad.size() == LightSize4x4)
|
||||
sLightsDataForPad.append(LightSize25 - LightSize4x4, '\0');
|
||||
|
||||
// Lights are sent in three commands:
|
||||
//
|
||||
// 4: the 3x3 inner grid
|
||||
// 2: the top 4x2 lights
|
||||
// 3: the bottom 4x2 lights
|
||||
//
|
||||
// Command 4 is only used by firmware version 4+.
|
||||
//
|
||||
// Always send all three commands if the firmware expects it, even if we've
|
||||
// been given 4x4 data.
|
||||
sLightCommands[0][iPad] = "4";
|
||||
sLightCommands[1][iPad] = "2";
|
||||
sLightCommands[2][iPad] = "3";
|
||||
int iNextInputByte = 0;
|
||||
auto scaleLight = [](uint8_t iColor) {
|
||||
// Apply color scaling. Values over about 170 don't make the LEDs any brighter, so this
|
||||
@ -302,13 +332,22 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
|
||||
uint8_t iColor = sLightsDataForPad[iNextInputByte++];
|
||||
iColor = scaleLight(iColor);
|
||||
|
||||
int iCommandIndex = iByte < 4*2*3? 0:1;
|
||||
int iCommandIndex = iByte < 4*2*3? 1:2;
|
||||
sLightCommands[iCommandIndex][iPad].append(1, iColor);
|
||||
}
|
||||
|
||||
// Create the 4 command.
|
||||
for(int iByte = 0; iByte < 3*3*3; ++iByte)
|
||||
{
|
||||
uint8_t iColor = sLightsDataForPad[iNextInputByte++];
|
||||
iColor = scaleLight(iColor);
|
||||
sLightCommands[0][iPad].append(1, iColor);
|
||||
}
|
||||
}
|
||||
|
||||
sLightCommands[0][iPad].push_back('\n');
|
||||
sLightCommands[1][iPad].push_back('\n');
|
||||
sLightCommands[2][iPad].push_back('\n');
|
||||
}
|
||||
|
||||
// Each update adds one entry to m_aPendingLightsCommands for each lights command.
|
||||
@ -325,40 +364,92 @@ void SMX::SMXManager::SetLights(const string sPanelLights[2])
|
||||
// Note that m_aPendingLightsCommands contains the update for both pads, to guarantee
|
||||
// we always send light updates for both pads together and they never end up out of
|
||||
// phase.
|
||||
if(m_aPendingLightsCommands.size() < 3)
|
||||
{
|
||||
static const double fDelayBetweenLightsCommands = 1/60.0;
|
||||
|
||||
// There's a subtle but important difference between command timing in
|
||||
// firmware version 4 compared to earlier versions:
|
||||
//
|
||||
// Earlier firmwares would process host commands as soon as they're received.
|
||||
// Because of this, we have to wait before sending the '3' command to give
|
||||
// the master controller time to finish sending the '2' command to panels.
|
||||
// If we don't do this everything will still work, but the master will block
|
||||
// while processing the second command waiting for panel data to finish sending
|
||||
// since the TX queue will be full. If this happens it isn't processing HID
|
||||
// data, which reduces input timing accuracy.
|
||||
//
|
||||
// Firmware version 4 won't process a host command if there's data still being
|
||||
// sent to the panels. It'll wait until the data is flushed. This means that
|
||||
// we can queue all three lights commands at once, and just send them as fast
|
||||
// as the host acknowledges them. The second command will sit around on the
|
||||
// master controller's buffer until it finishes sending the first command to
|
||||
// the panels, then the third command will do the same.
|
||||
//
|
||||
// This change is only needed due to the larger amount of data sent in 25-light
|
||||
// mode. Since we're spending more time sending data from the master to the
|
||||
// panels, the timing requirements are tighter. Doing it in the same manual-delay
|
||||
// fashion causes too much latency and makes it harder to maintain 30 FPS.
|
||||
//
|
||||
// If two controllers are connected, they should either both be 4+ or not. We
|
||||
// don't handle the case where they're different and both timings are needed.
|
||||
double fNow = GetMonotonicTime();
|
||||
double fSendCommandAt = max(fNow, m_fDelayLightCommandsUntil);
|
||||
float fFirstCommandTime = fSendCommandAt;
|
||||
float fSecondCommandTime = fFirstCommandTime + fDelayBetweenLightsCommands;
|
||||
double fCommandTimes[3] = { fNow, fNow, fNow };
|
||||
|
||||
bool masterIsV4 = false;
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
SMXConfig config;
|
||||
if(m_pDevices[iPad]->GetConfigLocked(config) && config.masterVersion >= 4)
|
||||
masterIsV4 = true;
|
||||
}
|
||||
|
||||
// If we're on master firmware < 4, set delay times. For 4+, just queue commands.
|
||||
// We don't need to set fCommandTimes[0] since the '4' packet won't be sent.
|
||||
if(!masterIsV4)
|
||||
{
|
||||
const double fDelayBetweenLightsCommands = 1/60.0;
|
||||
fCommandTimes[1] = fSendCommandAt;
|
||||
fCommandTimes[2] = fCommandTimes[0] + fDelayBetweenLightsCommands;
|
||||
}
|
||||
|
||||
// Update m_fDelayLightCommandsUntil, so we know when the next
|
||||
m_fDelayLightCommandsUntil = fSecondCommandTime + fDelayBetweenLightsCommands;
|
||||
// lights command can be sent.
|
||||
m_fDelayLightCommandsUntil = fSendCommandAt + 1/30.0f;
|
||||
|
||||
// Add two commands to the list, scheduled at fFirstCommandTime and fSecondCommandTime.
|
||||
m_aPendingLightsCommands.push_back(PendingCommand(fFirstCommandTime));
|
||||
m_aPendingLightsCommands.push_back(PendingCommand(fSecondCommandTime));
|
||||
// Log(ssprintf("Scheduled commands at %f and %f", fFirstCommandTime, fSecondCommandTime));
|
||||
|
||||
// Wake up the I/O thread if it's blocking on WaitForMultipleObjectsEx.
|
||||
SetEvent(m_hEvent->value());
|
||||
// Add three commands to the list, scheduled at fFirstCommandTime and fSecondCommandTime.
|
||||
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[0]));
|
||||
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[1]));
|
||||
m_aPendingLightsCommands.push_back(PendingCommand(fCommandTimes[2]));
|
||||
}
|
||||
|
||||
// Set the pad commands.
|
||||
PendingCommand *pPendingCommands[2];
|
||||
pPendingCommands[0] = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-2]; // 2
|
||||
pPendingCommands[1] = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-1]; // 3
|
||||
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
// If the command for this pad is empty, leave any existing pad command alone.
|
||||
if(sLightCommands[0][iPad].empty())
|
||||
continue;
|
||||
|
||||
m_aPendingLightsCommands[0]->sPadCommand[iPad] = sLightCommands[0][iPad];
|
||||
m_aPendingLightsCommands[1]->sPadCommand[iPad] = sLightCommands[1][iPad];
|
||||
SMXConfig config;
|
||||
if(!m_pDevices[iPad]->GetConfigLocked(config))
|
||||
continue;
|
||||
|
||||
// If this pad is firmware version 4, send the 4 command. Otherwise, leave the 4 command
|
||||
// empty and no command will be sent.
|
||||
PendingCommand *pPending4Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-3]; // 3
|
||||
if(config.masterVersion >= 4)
|
||||
pPending4Commands->sPadCommand[iPad] = sLightCommands[0][iPad];
|
||||
else
|
||||
pPending4Commands->sPadCommand[iPad] = "";
|
||||
|
||||
PendingCommand *pPending2Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-2]; // 2
|
||||
pPending2Commands->sPadCommand[iPad] = sLightCommands[1][iPad];
|
||||
|
||||
PendingCommand *pPending3Commands = &m_aPendingLightsCommands[m_aPendingLightsCommands.size()-1]; // 3
|
||||
pPending3Commands->sPadCommand[iPad] = sLightCommands[2][iPad];
|
||||
}
|
||||
|
||||
// Wake up the I/O thread if it's blocking on WaitForMultipleObjectsEx.
|
||||
SetEvent(m_hEvent->value());
|
||||
}
|
||||
|
||||
void SMX::SMXManager::ReenableAutoLights()
|
||||
|
Loading…
x
Reference in New Issue
Block a user