From c382cc04ca347f1676e40d21e0e9d382efb47724 Mon Sep 17 00:00:00 2001 From: Vincent Bousquet Date: Sat, 24 Aug 2024 21:16:58 +0200 Subject: [PATCH] Add data exchange through a global shared state block This commits allows to share prviously unshared data without overloading too much the API by adding a single data block for pulsed state, device state, modulated segments, multiple DMDs, raw DMD frames (for correct rendering and allow coloring) and video. This commit contains the skeleton and base implementation to set up the ground for more testing on client side (VPX) when support will be more advanced there. It also gives a ground to implement in LibPinMame. --- src/win32com/Controller.cpp | 30 +++ src/win32com/Controller.h | 3 + src/win32com/VPinMAME.idl | 2 + src/wpc/core.c | 495 +++++++++++++++++++++++++++++++++++- src/wpc/core.h | 80 ++++++ 5 files changed, 604 insertions(+), 6 deletions(-) diff --git a/src/win32com/Controller.cpp b/src/win32com/Controller.cpp index e30ea3f59..c36c0874f 100644 --- a/src/win32com/Controller.cpp +++ b/src/win32com/Controller.cpp @@ -1733,6 +1733,36 @@ STDMETHODIMP CController::put_TimeFence(double timeInS) return S_OK; } +/**************************************************************************** + * IController.get_StateBlock: returns a shared memory block name which holds + * a global state block prepended by the memory block size as an unsigned int + ****************************************************************************/ +STDMETHODIMP CController::get_StateBlock(/*[out, retval]*/ BSTR* pVal) +{ + if (!pVal) + return E_POINTER; + if (!Machine) + return E_FAIL; + if (core_getOutputState(CORE_STATE_REQMASK_ALL) == NULL) + return E_FAIL; + CComBSTR bsStateSharedMemName(TEXT("Local\\VPinMameStateBlock")); + *pVal = bsStateSharedMemName.Detach(); + return S_OK; +} + +/**************************************************************************** + * IController.UpdateStateBlock: Update requested outputs of the global state + * block + ****************************************************************************/ +STDMETHODIMP CController::UpdateStateBlock(/*[in, defaultvalue(0x1F)]*/ unsigned int updateMask) +{ + if (!Machine) + return E_FAIL; + if (core_getOutputState(updateMask) == NULL) + return E_FAIL; + return S_OK; +} + /**************************************************************************** * IController.Version (read-only): gets the program version of VPM ****************************************************************************/ diff --git a/src/win32com/Controller.h b/src/win32com/Controller.h index 3d03987b6..0f64c75d1 100644 --- a/src/win32com/Controller.h +++ b/src/win32com/Controller.h @@ -205,6 +205,9 @@ DECLARE_PROTECT_FINAL_CONSTRUCT() STDMETHOD(put_ModOutputType)(/*[in]*/ int output, /*[in]*/ int no, /*[in]*/ int newVal); STDMETHOD(put_TimeFence)(/*[in]*/ double fenceIns); + + STDMETHOD(get_StateBlock)(/*[out, retval]*/ BSTR* pVal); + STDMETHOD(UpdateStateBlock)(/*[in, defaultvalue(0x1F)]*/ unsigned int updateMask); }; #endif // !defined(AFX_Controller_H__D2811491_40D6_4656_9AA7_8FF85FD63543__INCLUDED_) diff --git a/src/win32com/VPinMAME.idl b/src/win32com/VPinMAME.idl index 89e38553f..6e42d12bd 100644 --- a/src/win32com/VPinMAME.idl +++ b/src/win32com/VPinMAME.idl @@ -259,6 +259,8 @@ import "ocidl.idl"; [propget, id(88), helpstring("property ModOutputType")] HRESULT ModOutputType([in] int output, [in] int no, [out, retval] int *pVal); [propput, id(88), helpstring("property ModOutputType")] HRESULT ModOutputType([in] int output, [in] int no, [in] int newVal); [propput, id(89), helpstring("property TimeFence")] HRESULT TimeFence([in] double timeInS); + [propget, id(90), helpstring("property StateBlock")] HRESULT StateBlock([out, retval] BSTR* pVal); + [id(91), helpstring("method UpdateStateBlock")] HRESULT UpdateStateBlock([in, defaultvalue(0x1F)] unsigned int updateMask); }; // WSHDlg and related interfaces diff --git a/src/wpc/core.c b/src/wpc/core.c index 1c2bc41a4..af1a36f6b 100644 --- a/src/wpc/core.c +++ b/src/wpc/core.c @@ -106,6 +106,13 @@ void vp_setDIP(int bank, int value) { } extern tGTS3locals GTS3locals; #endif +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif +#ifndef MAX +#define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + INLINE UINT8 saturatedByte(float v) { v *= 255.f; @@ -721,6 +728,26 @@ static struct { UINT8 lastLampMatrix[CORE_MAXLAMPCOL]; int lastGI[CORE_MAXGI]; UINT64 lastSol; + /*-- Global output state block --*/ + core_tGlobalOutputState* outputStateBlock; // Global output state block, shared with clients of PinMame + struct { // Map between display layout (DMD/Video) and corresponding output frame + struct core_dispLayout* layout; + core_tFrameState* frame; + } displayStateBlocks[32]; + struct { // Map between alpha layout and corresponding output device + struct core_dispLayout* layout; + core_tDeviceSingleState* state; // Pointer to the first device state + } alphaStateBlocks[32]; + struct + { + int type; // Bit0 = unused/wired, Bit1 = Solenoid/GI, Bit2 = core_getSol or coreGlobals.GI/coreGlobals.physicOutputState + int solIndex; // Solenoid index (note that this is 1 based, so 1..CORE_MODOUT_SOL_MAX) + int physOutIndex; // Physic Output index, see CORE_MODOUT_SOL0, CORE_MODOUT_GI0,... + int giIndex; // GI index (note that this is 0 based) + } controlledOutputMapping[CORE_MODOUT_SOL_MAX + CORE_MODOUT_GI_MAX]; // Map between state block output and internal GI/Sol/PhysicOutput (see core_getAllSol): Positive = physic output / Negative = index for core_getSol() or coreGlobals.gi if <= -1000 / Unwired if -2000 +#ifdef VPINMAME + HANDLE outputStateSharedMem; // handle to the shared memory block used to share output states +#endif } locals; /*------------------------------- @@ -925,7 +952,7 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c // - if(layout->length >= 128) // Capcom hack + if(layout->length >= 128) // Only stores main DMD (skip small matrix display for example the 3 5x7 matrix in RBION) { g_raw_dmdx = layout->length; g_raw_dmdy = layout->start; @@ -943,6 +970,28 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c } #endif + if (locals.outputStateBlock && locals.outputStateBlock->displayState) + { + for (unsigned int i = 0; i < locals.outputStateBlock->displayState->nDisplays; i++) + { + if (locals.displayStateBlocks[i].layout == layout) + { + int offs = 0, frameChanged = 0; + for (ii = 1; ii <= layout->start; ii++) + for (jj = 0; jj < layout->length; jj++) + { + frameChanged |= locals.displayStateBlocks[i].frame->frameData[offs] != coreGlobals.dotCol[ii][jj]; + locals.displayStateBlocks[i].frame->frameData[offs] = coreGlobals.dotCol[ii][jj]; + offs++; + } + if (frameChanged) + locals.displayStateBlocks[i].frame->frameId++; + locals.displayStateBlocks[i].frame->updateTimestamp = timer_get_time(); + break; + } + } + } + memset(&coreGlobals.dotCol[layout->start+1][0], 0, sizeof(coreGlobals.dotCol[0][0])*layout->length+1); memset(&coreGlobals.dotCol[0][0], 0, sizeof(coreGlobals.dotCol[0][0])*layout->length+1); // clear above for (ii = 0; ii < layout->start+1; ii++) { @@ -957,7 +1006,7 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c #ifdef LIBPINMAME g_raw_dmdbuffer[offs] = (g_fDmdMode == 0) ? (shade_16_enabled ? raw_16[col] : raw_4[col]) : col; #else - if(layout->length >= 128) { // Capcom hack + if(layout->length >= 128) { // Only stores main DMD (skip small matrix display for example the 3 5x7 matrix in RBION) g_raw_dmdbuffer[offs + raw_dmdoffs] = shade_16_enabled ? raw_16[col] : raw_4[col]; g_raw_colordmdbuffer[offs + raw_dmdoffs] = shade_16_enabled ? palette32_16[col] : palette32_4[col]; } @@ -982,9 +1031,8 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c } #ifdef VPINMAME - if ((layout->length == 128) || (layout->length == 192) || (layout->length == 256)) { // filter 16x8 output from Flipper Football - //external dmd - if (g_fShowPinDMD) + if ((layout->length == 128) || (layout->length == 192) || (layout->length == 256)) { // Only sends main DMD to auxiliary DLL & frame capture (skip small matrix display for example the 3 5x7 matrix in RBION) + if (g_fShowPinDMD) //external dmd { if (strncasecmp(Machine->gamedrv->name, "snspare", 7) == 0) { @@ -1363,7 +1411,7 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl if (oldbuffer != NULL && memcmp(oldbuffer, currbuffer, g_raw_dmdx*g_raw_dmdy) != 0) { - for (i = 0; i < g_raw_dmdx*g_raw_dmdy; ++i) + for (i = 0; i < (int)g_raw_dmdx* (int)g_raw_dmdy; ++i) g_raw_dmdbuffer[i] = (UINT8)((int)currbuffer[i]*100/15); g_needs_DMD_update = 1; } @@ -2074,6 +2122,423 @@ int core_getDip(int dipBank) { #endif /* VPINMAME */ } +/*------------------------------------------------- +/ Lazily create and update a mem block holding output state +/--------------------------------------------------*/ +void core_createStateBlock() +{ + assert(locals.outputStateBlock == NULL); + + const unsigned int headerSize = sizeof(core_tGlobalOutputState); + const unsigned int controlledOutputBinarySize = sizeof(core_tBinaryState) + ((((CORE_MODOUT_SOL_MAX + 7) / 8) + 3) & ~3); + const unsigned int controlledOutputDeviceSize = sizeof(core_tDeviceState) + CORE_MODOUT_SOL_MAX * sizeof(core_tDeviceSingleState); // + const unsigned int lampMatrixDeviceSize = sizeof(core_tDeviceState) + (1 + CORE_MAXLAMPCOL * 8) * sizeof(core_tDeviceSingleState); // TODO CORE_MAXLAMPCOL does not really correspond to the lamp matrix it is also used by some drivers for other outputs (move to DMD or controlled outputs ?) + const unsigned int alphaDisplayDeviceSize = sizeof(core_tDeviceState) + CORE_MODOUT_SEG_MAX * sizeof(core_tDeviceSingleState); // + const unsigned int displayStateSize = sizeof(core_tDisplayState) + 5 * (sizeof(core_tFrameState) + (DMD_MAXX * DMD_MAXY)); + const unsigned int size = headerSize + + controlledOutputBinarySize + + controlledOutputDeviceSize + + lampMatrixDeviceSize + + alphaDisplayDeviceSize + + displayStateSize; + + #ifdef VPINMAME + // VPinMame uses Windows shared memory to share the state block, so we need to allocate & map it as such + static TCHAR szName[] = TEXT("Local\\VPinMameStateBlock"); + locals.outputStateSharedMem = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size + sizeof(unsigned int), szName); + if (locals.outputStateSharedMem == NULL) + return; + UINT8* sharedMem = (UINT8*)MapViewOfFile(locals.outputStateSharedMem, FILE_MAP_WRITE, 0, 0, size + sizeof(unsigned int)); + if (sharedMem == NULL) + { + CloseHandle(locals.outputStateSharedMem); + locals.outputStateSharedMem = INVALID_HANDLE_VALUE; + return; + } + *((unsigned int*)sharedMem) = size; + locals.outputStateBlock = (core_tGlobalOutputState*)(sharedMem + sizeof(unsigned int)); + #else + locals.outputStateBlock = (core_tGlobalOutputState*)malloc(size); + #endif + UINT8* data = (UINT8*)locals.outputStateBlock; + memset(data, 0, size); + + // Overall header. We use a version id here instead of the main PinMame version to allow changing it during development phase + locals.outputStateBlock->versionID = 1; + data += headerSize; + + // Controlled outputs: all general purpose controlled outputs (usually solenoids, but also flashers, motors, child boards, GI,...) with a fairly messy mapping + locals.outputStateBlock->controlledOutputBinaryState = (core_tBinaryState*)data; + locals.outputStateBlock->controlledOutputBinaryState->nOutputs = MIN(32, coreGlobals.nGI + (coreGlobals.nSolenoids ? coreGlobals.nSolenoids : (CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol))); + data += controlledOutputBinarySize; + locals.outputStateBlock->controlledOutputDeviceState = (core_tDeviceState*)data; + locals.outputStateBlock->controlledOutputDeviceState->nDevices = coreGlobals.nGI + (coreGlobals.nSolenoids ? coreGlobals.nSolenoids : (CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol)); + locals.outputStateBlock->controlledOutputDeviceState->dataStride = sizeof(core_tDeviceSingleState); + data += controlledOutputDeviceSize; + memset(locals.controlledOutputMapping, 0, sizeof(locals.controlledOutputMapping)); + if (coreGlobals.nSolenoids && (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_SOLENOIDS | CORE_MODOUT_ENABLE_MODSOL))) + { + /*-- 1..32, hardware solenoids --*/ + if (core_gameData->gen & GEN_ALLWPC) { + for (int i = 0; i < 28; i++) + { + locals.controlledOutputMapping[i].type = 5; + locals.controlledOutputMapping[i].solIndex = i + 1; + locals.controlledOutputMapping[i].physOutIndex = CORE_MODOUT_SOL0 + i; + } + // 29..32 GameOn (not modulated, stored in 0x0F00 of solenoids2) + for (int i = 28; i < 32; i++) + { + locals.controlledOutputMapping[i].type = 1; + locals.controlledOutputMapping[i].solIndex = i + 1; // mapping from 29..32 to solenoids2 bit is done in core_getSol + } + } + else + for (int i = 0; i < 32; i++) + { + locals.controlledOutputMapping[i].type = 5; + locals.controlledOutputMapping[i].solIndex = i + 1; + locals.controlledOutputMapping[i].physOutIndex = CORE_MODOUT_SOL0 + i; + } + /*-- 33..36 upper flipper solenoids (not modulated for the time being) --*/ + for (int i = 32; i < 36; i++) + { + locals.controlledOutputMapping[i].type = 1; + locals.controlledOutputMapping[i].solIndex = i + 1; + } + /*-- 37..44, extra solenoids --*/ + if (core_gameData->gen & (GEN_WPC95 | GEN_WPC95DCS)) { // 37-44 WPC95 extra (duplicated 37..40 / 41..44) + for (int i = 36; i < 44; i++) + { + locals.controlledOutputMapping[i].type = 5; + locals.controlledOutputMapping[i].solIndex = 29 + ((i - 36) & 3); // Maps to 29..32 + locals.controlledOutputMapping[i].physOutIndex = CORE_MODOUT_SOL0 + 28 + ((i - 36) & 3); + } + } + else if (core_gameData->gen & (GEN_ALLS11 | GEN_SAM | GEN_SPA)) // 37-44 S11, SAM extra + for (int i = 36; i < 44; i++) + { + locals.controlledOutputMapping[i].type = 5; + locals.controlledOutputMapping[i].solIndex = i + 4; + locals.controlledOutputMapping[i].physOutIndex = CORE_MODOUT_SOL0 + i + 4; + } + /*-- 45..48 lower flipper solenoids (not modulated for the time being) --*/ + for (int i = 44; i < 48; i++) + { + locals.controlledOutputMapping[i].type = 1; + locals.controlledOutputMapping[i].solIndex = i + 1; + } + /*-- 49..50 simulated (50 is unused so far) --*/ + { + locals.controlledOutputMapping[48].type = 1; + locals.controlledOutputMapping[48].solIndex = 49; + } + /*-- 51..66 custom --*/ + for (int i = CORE_FIRSTCUSTSOL - 1; i < CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol; i++) + { + locals.controlledOutputMapping[i].type = i < coreGlobals.nSolenoids ? 5 : 1; + locals.controlledOutputMapping[i].solIndex = i + 1; + locals.controlledOutputMapping[i].physOutIndex = CORE_MODOUT_SOL0 + i; + } + } + else + { + for (int i = 0; i < CORE_FIRSTCUSTSOL + core_gameData->hw.custSol - 1; i++) + { + locals.controlledOutputMapping[i].type = 1; + locals.controlledOutputMapping[i].solIndex = i + 1; + } + } + /*-- WPC, Sega/Stern Whitestar and Stern SAM GI are added to the tail --*/ + // GI are controlled outputs dedicated to GI that WPC and Sega/Stern Whitestar drivers declare (other drivers do not declare them separately and treat them as regular controlled outputs) + // . 3 to 5 are declared for WPC with a 'modulated' value between 0 and 8 (legacy implementation is somewhat buggy regarding this value) + // . 1 is declared for Sega/Stern Whitestar and Stern SAM with a value of either 0 or 9 + for (int i = 0; i < coreGlobals.nGI; i++) + { + locals.controlledOutputMapping[CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol + i].type = (coreGlobals.nGI && (options.usemodsol & CORE_MODOUT_ENABLE_PHYSOUT_GI)) ? 7 : 3; + locals.controlledOutputMapping[CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol + i].giIndex = i; + locals.controlledOutputMapping[CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol + i].physOutIndex = CORE_MODOUT_GI0 + i; + } + for (unsigned int i = 0; i < locals.outputStateBlock->controlledOutputDeviceState->nDevices; i++) + { + const int outputType = locals.controlledOutputMapping[i].type; + if ((outputType & 5) == 1) // 'Legacy' Solenoid & GI + locals.outputStateBlock->controlledOutputDeviceState->states[i].deviceType = CORE_DEVICE_STATE_TYPE_CUSTOM; + else if ((outputType & 5) == 5) // Physic Output Solenoid & GI + { + const int output = locals.controlledOutputMapping[i].physOutIndex; + const int type = coreGlobals.physicOutputState[output].type; + if (options.usemodsol & CORE_MODOUT_ENABLE_MODSOL) + locals.outputStateBlock->controlledOutputDeviceState->states[i].deviceType = CORE_DEVICE_STATE_TYPE_CUSTOM; + else if (type >= CORE_MODOUT_BULB_44_6_3V_AC && type <= CORE_MODOUT_BULB_906_25V_DC_S11) + locals.outputStateBlock->controlledOutputDeviceState->states[i].deviceType = CORE_DEVICE_STATE_TYPE_BULB; + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->controlledOutputDeviceState->states[i].deviceType = CORE_DEVICE_STATE_TYPE_LED; + else + locals.outputStateBlock->controlledOutputDeviceState->states[i].deviceType = CORE_DEVICE_STATE_TYPE_CUSTOM; + } + } + + // Lamp matrix: strobed lamp matrix (but some drivers also uses this for additional lamps/outputs) + locals.outputStateBlock->lampMatrixState = (core_tDeviceState*)data; + locals.outputStateBlock->lampMatrixState->nDevices = 0; + const int hasSAMModulatedLeds = (core_gameData->gen & GEN_SAM) && (core_gameData->hw.lampCol > 2); + const int nLamps = (hasSAMModulatedLeds || coreGlobals.nLamps) ? coreGlobals.nLamps : (8 * (CORE_CUSTLAMPCOL + core_gameData->hw.lampCol)); + for (int i = 0; i < nLamps; i++) + { + const unsigned int lampNo = coreData->m2lamp ? coreData->m2lamp((i / 8) + 1, i & 7) : i; + locals.outputStateBlock->lampMatrixState->nDevices = MAX(locals.outputStateBlock->lampMatrixState->nDevices, lampNo); + assert(locals.outputStateBlock->lampMatrixState->nDevices < (1 + CORE_MAXLAMPCOL * 8)); // This assert is needed since we can not guarantee that the driver will map the lamps inside the max range + if (coreGlobals.nLamps && (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_LAMPS))) + { + const int type = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].type; + if (type >= CORE_MODOUT_BULB_44_6_3V_AC && type <= CORE_MODOUT_BULB_906_25V_DC_S11) + locals.outputStateBlock->controlledOutputDeviceState->states[lampNo].deviceType = CORE_DEVICE_STATE_TYPE_BULB; + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->controlledOutputDeviceState->states[lampNo].deviceType = CORE_DEVICE_STATE_TYPE_LED; + else + locals.outputStateBlock->controlledOutputDeviceState->states[lampNo].deviceType = CORE_DEVICE_STATE_TYPE_CUSTOM; + } + else + locals.outputStateBlock->controlledOutputDeviceState->states[lampNo].deviceType = CORE_DEVICE_STATE_TYPE_CUSTOM; + } + locals.outputStateBlock->lampMatrixState->dataStride = sizeof(core_tDeviceSingleState); + data += lampMatrixDeviceSize; + + // Alpha numeric segment displays + locals.outputStateBlock->alphaDisplayState = (core_tDeviceState*)data; + locals.outputStateBlock->alphaDisplayState->nDevices = 0; // FIXME implement + locals.outputStateBlock->alphaDisplayState->dataStride = sizeof(core_tDeviceSingleState); + data += alphaDisplayDeviceSize; + + // All Displays: this includes main DMD but also small LED matrix display (WOF, WPT, RBION,...) and video displays (Caveman, MrGame machines,...) + locals.outputStateBlock->displayState = (core_tDisplayState*)data; + locals.outputStateBlock->displayState->nDisplays = 0; + data += displayStateSize; + + // Gather DMD/Video and alpha numeric display informations and mapping + core_tFrameState* nextDisplayFrame = (core_tFrameState*)((UINT8*)locals.outputStateBlock->displayState + sizeof(core_tDisplayState)); + const int dmd16Shades = ((core_gameData->gen & (GEN_SAM | GEN_SPA | GEN_ALVG_DMD2)) || + // special handling for some GTS3 games with shades computed across multiple frames (SMB, SMBMW and CBW): + (strncasecmp(Machine->gamedrv->name, "smb", 3) == 0) || (strncasecmp(Machine->gamedrv->name, "cueball", 7) == 0)); + struct core_dispLayout* layout = core_gameData->lcdLayout; + struct core_dispLayout* parent_layout = NULL; + for (; layout->length || (parent_layout && parent_layout->length); layout += 1) { + if (layout->length == 0) + { + layout = parent_layout; + parent_layout = NULL; + } + switch (layout->type & CORE_SEGMASK) + { + case CORE_IMPORT: + assert(parent_layout == NULL); // IMPORT of IMPORT is not currently supported as it is not used by any driver so far + parent_layout = layout + 1; + layout = layout->lptr - 1; + break; + case CORE_VIDEO: // Video display for games like Baby PacMan, frames are stored as RGB8 + case CORE_DMD: // DMD displays and LED matrices (for example RBION,... search for CORE_NODISP to list them) + locals.displayStateBlocks[locals.outputStateBlock->displayState->nDisplays].layout = layout; + locals.displayStateBlocks[locals.outputStateBlock->displayState->nDisplays].frame = nextDisplayFrame; + nextDisplayFrame->structSize = sizeof(core_tFrameState) + layout->length * layout->start * 3; + nextDisplayFrame->width = layout->length; + nextDisplayFrame->height = layout->start; + if ((layout->type & CORE_SEGMASK) == CORE_VIDEO) + nextDisplayFrame->dataFormat = CORE_VIDEO_FRAME_RGB; + else + nextDisplayFrame->dataFormat = dmd16Shades ? CORE_DMD_FRAME_LUM16 : CORE_DMD_FRAME_LUM4; + nextDisplayFrame = (core_tFrameState*)(((UINT8*)nextDisplayFrame) + nextDisplayFrame->structSize); + assert(((UINT8*)nextDisplayFrame - (UINT8*)locals.outputStateBlock->displayState) < displayStateSize); + locals.outputStateBlock->displayState->nDisplays++; + break; + default: // Alphanumeric segment displays + locals.alphaStateBlocks[locals.outputStateBlock->alphaDisplayState->nDevices].layout = layout; + locals.alphaStateBlocks[locals.outputStateBlock->alphaDisplayState->nDevices].state = &(locals.outputStateBlock->alphaDisplayState->states[locals.outputStateBlock->alphaDisplayState->nDevices]); + locals.outputStateBlock->alphaDisplayState->states[locals.outputStateBlock->alphaDisplayState->nDevices].segment.type = layout->type & CORE_SEGALL; + locals.outputStateBlock->alphaDisplayState->nDevices++; + break; + } + } +} + +core_tGlobalOutputState* core_getOutputState(const unsigned int updateMask) +{ + if (locals.outputStateBlock == NULL) + { + core_createStateBlock(); + if (locals.outputStateBlock == NULL) + return NULL; + } + + // HACK as timer_get_time is not thread safe and can only be called from emulation thread. + // We use a fake timer with timer_starttime which is thread safe as a workaround. + // const double now = timer_get_time(); + mame_timer fake_timer = { 0 }; + const double now = timer_starttime(&fake_timer); + + if (updateMask & CORE_STATE_REQMASK_GPOUTPUT_BINARY_STATE) + { + locals.outputStateBlock->controlledOutputBinaryState->updateTimestamp = now; + locals.outputStateBlock->controlledOutputBinaryState->outputBitset[0] = 0; + for (unsigned int i = 0; i < locals.outputStateBlock->controlledOutputBinaryState->nOutputs; i++) + { + if ((locals.controlledOutputMapping[i].type == 1 || locals.controlledOutputMapping[i].type == 3) // Only for solenoids + && core_getPulsedSol(locals.controlledOutputMapping[i].solIndex)) // We need core_getPulsedSol to perform its mapping for WPC hardware + locals.outputStateBlock->controlledOutputBinaryState->outputBitset[0] |= 1 << i; + } + } + + if (updateMask & CORE_STATE_REQMASK_GPOUTPUT_DEVICE_STATE) + { + core_update_pwm_solenoids(); + core_update_pwm_gis(); + locals.outputStateBlock->controlledOutputDeviceState->updateTimestamp = now; + for (unsigned int i = 0; i < locals.outputStateBlock->controlledOutputDeviceState->nDevices; i++) + { + switch (locals.controlledOutputMapping[i].type) + { + case 1: // Solenoid using core_getSol + locals.outputStateBlock->controlledOutputDeviceState->states[i].customState = core_getSol(locals.controlledOutputMapping[i].solIndex) ? 1 : 0; + break; + case 3: // GI using coreGlobals.gi + locals.outputStateBlock->controlledOutputDeviceState->states[i].customState = coreGlobals.gi[locals.controlledOutputMapping[i].solIndex]; + break; + case 5: // Solenoid using physic output + case 7: // GI using physic output + { + const int output = locals.controlledOutputMapping[i].physOutIndex; + const int type = coreGlobals.physicOutputState[output].type; + if (options.usemodsol & CORE_MODOUT_ENABLE_MODSOL) + locals.outputStateBlock->controlledOutputDeviceState->states[i].customState = saturatedByte(coreGlobals.physicOutputState[output].value); + else if (type >= CORE_MODOUT_BULB_44_6_3V_AC && type <= CORE_MODOUT_BULB_906_25V_DC_S11) + { + locals.outputStateBlock->controlledOutputDeviceState->states[i].bulb.luminance = coreGlobals.physicOutputState[output].value; + locals.outputStateBlock->controlledOutputDeviceState->states[i].bulb.filamentTemperature = 0.f; // Not yet implemented + } + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->controlledOutputDeviceState->states[i].ledLuminance = coreGlobals.physicOutputState[output].value; + else + locals.outputStateBlock->controlledOutputDeviceState->states[i].customState = saturatedByte(coreGlobals.physicOutputState[output].value); + } + break; + } + } + } + + if (updateMask & CORE_STATE_REQMASK_LAMP_DEVICE_STATE) + { + core_update_pwm_lamps(); + locals.outputStateBlock->lampMatrixState->updateTimestamp = now; + if (coreGlobals.nLamps && (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_LAMPS))) + { + for (unsigned int i = 0; i < locals.outputStateBlock->lampMatrixState->nDevices; i++) + { + const unsigned int lampNo = coreData->m2lamp ? coreData->m2lamp((i / 8) + 1, i & 7) : i; + const int type = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].type; + if (type >= CORE_MODOUT_BULB_44_6_3V_AC && type <= CORE_MODOUT_BULB_906_25V_DC_S11) + locals.outputStateBlock->lampMatrixState->states[lampNo].bulb.luminance = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value; + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->lampMatrixState->states[lampNo].ledLuminance = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value; + else + locals.outputStateBlock->lampMatrixState->states[lampNo].customState = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value); + } + } + else + { + const int hasSAMModulatedLeds = (core_gameData->gen & GEN_SAM) && (core_gameData->hw.lampCol > 2); + const unsigned int nCol = CORE_STDLAMPCOLS + (hasSAMModulatedLeds ? 2 : core_gameData->hw.lampCol); + for (unsigned int col = 0; col < nCol; col++) + for (unsigned int row = 0, rowLamp = coreGlobals.lampMatrix[col]; row < 8; row++, rowLamp >>= 1) + { + const unsigned int lampNo = coreData->m2lamp ? coreData->m2lamp(col + 1, row) : (col * 8 + row); + locals.outputStateBlock->lampMatrixState->states[lampNo].customState = rowLamp & 0x01; + } + // Backward compatibility for modulated LED & RGB LEDs of SAM hardware + if (hasSAMModulatedLeds) + for (unsigned int i = 80; i < (unsigned int)coreGlobals.nLamps; i++) + locals.outputStateBlock->lampMatrixState->states[i].customState = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value); + } + } + + if (updateMask & CORE_STATE_REQMASK_ALPHA_DEVICE_STATE) + { + static int nSegments[] = { 16, 16, 9, 9, 7, 7, 7, 7, 7, 9, 9, 7, 7, 16, -1, -1, 16, 16 }; + core_update_pwm_segments(); + locals.outputStateBlock->alphaDisplayState->updateTimestamp = now; + for (unsigned int i = 0; i < locals.outputStateBlock->alphaDisplayState->nDevices; i++) + { + struct core_dispLayout* layout = locals.alphaStateBlocks[i].layout; + core_tDeviceSingleState* state = locals.alphaStateBlocks[i].state; + const int nSegs = nSegments[layout->type]; + assert(nSegs >= 7 && nSegs <= 16); + assert(layout->type != CORE_DMD && layout->type != CORE_VIDEO); + if (options.usemodsol & (CORE_MODOUT_FORCE_ON | CORE_MODOUT_ENABLE_PHYSOUT_ALPHASEGS)) + { + // FIXME implement hibit + // FIXME implement forced comma (rely on client to handle them ?) + // FIXME implement 9 to 8 segments display conversion for modulated alpha display (rely on client to handle this ?) + const int step = (layout->type & CORE_SEGREV) ? -16 : 16; + int pos = CORE_MODOUT_SEG0 + layout->start * 16; + if (layout->type & CORE_SEGREV) { pos += (layout->length - 1) * 16; } + for (int i = 0; i < layout->length; i++, pos += step) // Loop over each character (up to 20) + { + for (int j = 0; j < nSegs; j++) // Loop over each segments of the current character (up to 16) + state->segment.luminance[j] = coreGlobals.physicOutputState[pos + j].value; + state++; + } + } + else + { + UINT16* seg = &coreGlobals.segments[layout->start].w; + if (layout->type & CORE_SEGREV) { seg += layout->length - 1; } + const int step = (layout->type & CORE_SEGREV) ? -1 : 1; + const int type = layout->type & CORE_SEGMASK; + for (int i = 0; i < layout->length; i++) // Loop over each character (up to 20) + { + UINT16 tmpSeg = *seg; + if (layout->type & CORE_SEGHIBIT) tmpSeg >>= 8; + switch (layout->type & CORE_SEGMASK) { + case CORE_SEG87: + case CORE_SEG87F: + if (tmpSeg && (type == CORE_SEG87F) && (i > 0) && (i % 3 == 0)) tmpSeg |= 0x80; // Forced comma every 3 digits if at least one segment is on + break; + case CORE_SEG98: + case CORE_SEG98F: + if (tmpSeg && (type == CORE_SEG98F) && (i > 0) && (i % 3 == 0)) tmpSeg |= 0x80; // Forced comma every 3 digits if at least one segment is on + case CORE_SEG9: + tmpSeg |= (tmpSeg & 0x100) << 1; // The 9th segment is driven by the 8th (rely on client to handle this ?) + break; + } + for (int j = 0; j < nSegs; j++, tmpSeg >>= 1) // Loop over each segments of the current character (up to 16) + state->segment.luminance[j] = (tmpSeg & 1) ? 1.f : 0.f; + seg += step; + state++; + } + } + } + } + + if (updateMask & CORE_STATE_REQMASK_DISPLAY_STATE) + { + for (unsigned int i = 0; i < locals.outputStateBlock->displayState->nDisplays; i++) + { + struct core_dispLayout* layout = locals.displayStateBlocks[i].layout; + // Nothing to do for DMD and LED matrix as they are directly updated in video_update_core_dmd (to get the raw data instead of rendered one, needed for correct shading and coloring plugins) + if (layout->fptr && layout->type == CORE_VIDEO) + { + core_tFrameState* frame = locals.displayStateBlocks[i].frame; + frame->updateTimestamp = now; + // TODO implement video displays + //if (((ptPinMAMEvidUpdate)(layout->fptr))(bitmap, cliprect, layout) == 0) + { + } + } + } + } + + return locals.outputStateBlock; +} + /*-------------------- / Draw a LED digit /---------------------*/ @@ -2273,6 +2738,9 @@ static MACHINE_INIT(core) { // DMD USB Init if(g_fShowPinDMD && !time_to_reset) pindmdInit(g_szGameName, core_gameData->gen, &pmoptions); + + locals.outputStateBlock = NULL; + locals.outputStateSharedMem = INVALID_HANDLE_VALUE; #endif OnStateChange(1); /* We have a lift-off */ @@ -2288,6 +2756,21 @@ static MACHINE_STOP(core) { // DMD USB Kill if(g_fShowPinDMD && !time_to_reset) pindmdDeInit(); + + if (locals.outputStateSharedMem != INVALID_HANDLE_VALUE) + { + UINT8* sharedMem = (UINT8*)(locals.outputStateBlock) - sizeof(unsigned int); + UnmapViewOfFile(sharedMem); + CloseHandle(locals.outputStateSharedMem); + locals.outputStateSharedMem = INVALID_HANDLE_VALUE; + locals.outputStateBlock = NULL; + } +#else + if (locals.outputStateBlock) + { + free(locals.outputStateBlock); + locals.outputStateBlock = NULL; + } #endif #if defined(VPINMAME) || defined(LIBPINMAME) diff --git a/src/wpc/core.h b/src/wpc/core.h index b19ad8a27..7d0958ebf 100644 --- a/src/wpc/core.h +++ b/src/wpc/core.h @@ -583,6 +583,86 @@ extern int core_getPulsedSol(int solNo); extern UINT64 core_getAllSol(void); extern void core_getAllPhysicSols(float* const state); +/*-- full output state --*/ +#pragma warning(disable : 4200) // 0 length array is a non standard extension used intentionally, so disable corresponding warning +typedef struct +{ + double updateTimestamp; + unsigned int nOutputs; + UINT32 outputBitset[]; // Bitset array of nOutputs bits with their current binary state +} core_tBinaryState; +#define CORE_DEVICE_STATE_TYPE_CUSTOM 1 // Custom state defined by each driver (value maybe either binary of 0/1 or 0/255, or modulated between 0..255) +#define CORE_DEVICE_STATE_TYPE_BULB 2 // Bulb state defined by its relative luminance and average filament temperature +#define CORE_DEVICE_STATE_TYPE_LED 3 // LED state defined by its relative luminance +#define CORE_DEVICE_STATE_TYPE_SEGMENTS 4 // LED or VFD state defined by a segment layout and the relative luminance of each segment +typedef struct +{ + unsigned int deviceType; + union + { + // CORE_DEVICE_STATE_TYPE_DS + UINT8 customState; // Custom value, depending on each driver definition + // CORE_DEVICE_STATE_TYPE_BULB + struct incandescentBulbState + { + float luminance; // relative luminance to bulb rating (equals 1.f when bulb is under its rating voltage after heating stabilization) + float filamentTemperature; // perceived filament temperature (equals to bulb filament rating when bulb is under its rating voltage after heating stabilization) + } bulb; + // CORE_DEVICE_STATE_TYPE_LED + float ledLuminance; // relative luminance to bulb design (equals 1.f when LED is pulsed at its designed PWM) + // CORE_DEVICE_STATE_TYPE_SEGMENTS + struct segmentBlockState + { + unsigned int type; // see CORE_SEG16, ... + float luminance[16]; // relative luminance of each segment (from 7 to 16) + } segment; + }; +} core_tDeviceSingleState; +#pragma warning(disable : 4200) // 0 length array is a non standard extension used intentionally, so disable corresponding warning +typedef struct +{ + double updateTimestamp; + unsigned int nDevices; + unsigned int dataStride; + core_tDeviceSingleState states[]; // array of nDevices * dataStride with the current device state +} core_tDeviceState; +#define CORE_DMD_FRAME_LUM4 1 // Raw internal DMD luminance value using 4 shades (0..3) +#define CORE_DMD_FRAME_LUM16 2 // Raw internal DMD luminance value using 16 shades (0..15) +#define CORE_VIDEO_FRAME_RGB 3 // Raw RGB (TODO sRGB ? to be validated) video frame +#pragma warning(disable : 4200) // 0 length array is a non standard extension used intentionally, so disable corresponding warning +typedef struct +{ + unsigned int structSize; // Struct size including header and frame data in bytes (for safe DMD/Display array iteration) + double updateTimestamp; + unsigned int width; + unsigned int height; + unsigned int dataFormat; + unsigned int frameId; + UINT8 frameData[]; // The display frame data which size depends on width, height and data format +} core_tFrameState; +typedef struct +{ + unsigned int nDisplays; + // core_tFrameState displays[]; // Array of nDisplays * core_tFrameState (can't be directly declared since frame size is undefined) +} core_tDisplayState; +typedef struct +{ + unsigned int versionID; + core_tBinaryState* controlledOutputBinaryState; + core_tDeviceState* controlledOutputDeviceState; + core_tDeviceState* lampMatrixState; + core_tDeviceState* alphaDisplayState; + core_tDisplayState* displayState; +} core_tGlobalOutputState; + +#define CORE_STATE_REQMASK_GPOUTPUT_BINARY_STATE 0x01 +#define CORE_STATE_REQMASK_GPOUTPUT_DEVICE_STATE 0x02 +#define CORE_STATE_REQMASK_LAMP_DEVICE_STATE 0x04 +#define CORE_STATE_REQMASK_ALPHA_DEVICE_STATE 0x08 +#define CORE_STATE_REQMASK_DISPLAY_STATE 0x10 +#define CORE_STATE_REQMASK_ALL 0x1F +extern core_tGlobalOutputState* core_getOutputState(const unsigned int updateMask); + /*-- AC sync and PWM integration --*/ extern void core_update_pwm_outputs(const int startIndex, const int count); INLINE void core_update_pwm_gis() { if (options.usemodsol & (CORE_MODOUT_FORCE_ON | CORE_MODOUT_ENABLE_PHYSOUT_GI)) core_update_pwm_outputs(CORE_MODOUT_GI0, coreGlobals.nGI); }