From 640742838065d91263a0c87c2f11f7301ce8ba68 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 previously unshared data without overloading too much the API by adding a single data block for pulsed state, device state, modulated segments, video displays, multiple DMDs with raw DMD frames (for correct rendering and colorization). 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. --- cmake/libpinmame/CMakeLists.txt | 1 + cmake/pinmame/CMakeLists_win-x64.txt | 1 + cmake/pinmame/CMakeLists_win-x86.txt | 1 + cmake/pinmame32/CMakeLists_win-x64.txt | 1 + cmake/pinmame32/CMakeLists_win-x86.txt | 1 + cmake/vpinmame/CMakeLists_win-x64.txt | 1 + cmake/vpinmame/CMakeLists_win-x86.txt | 1 + cmake/xpinmame/CMakeLists_linux-x64.txt | 1 + cmake/xpinmame/CMakeLists_osx-x64.txt | 1 + src/libpinmame/libpinmame.cpp | 28 +- src/libpinmame/libpinmame.h | 38 +- src/libpinmame/pinmamedef.h | 170 ++++++ src/libpinmame/test.cpp | 26 +- src/win32com/Controller.cpp | 30 ++ src/win32com/Controller.h | 3 + src/win32com/ControllerDmdDevice.cpp | 6 +- src/win32com/VPinMAME.idl | 2 + src/wpc/byvidpin.c | 6 + src/wpc/core.c | 675 +++++++++++++++++++++++- src/wpc/core.h | 43 +- src/wpc/sam.c | 8 +- src/wpc/wpc.c | 28 +- 22 files changed, 992 insertions(+), 80 deletions(-) create mode 100644 src/libpinmame/pinmamedef.h diff --git a/cmake/libpinmame/CMakeLists.txt b/cmake/libpinmame/CMakeLists.txt index e2abb1ddf..642cecc3b 100644 --- a/cmake/libpinmame/CMakeLists.txt +++ b/cmake/libpinmame/CMakeLists.txt @@ -504,6 +504,7 @@ set(PINMAME_SOURCES src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/pinmame/CMakeLists_win-x64.txt b/cmake/pinmame/CMakeLists_win-x64.txt index 6b7852a99..b67d1f717 100644 --- a/cmake/pinmame/CMakeLists_win-x64.txt +++ b/cmake/pinmame/CMakeLists_win-x64.txt @@ -444,6 +444,7 @@ add_executable(pinmame WIN32 src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/pinmame/CMakeLists_win-x86.txt b/cmake/pinmame/CMakeLists_win-x86.txt index ec4eddf47..2a50bc80b 100644 --- a/cmake/pinmame/CMakeLists_win-x86.txt +++ b/cmake/pinmame/CMakeLists_win-x86.txt @@ -450,6 +450,7 @@ add_executable(pinmame WIN32 src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/pinmame32/CMakeLists_win-x64.txt b/cmake/pinmame32/CMakeLists_win-x64.txt index d300c087c..963dd0a19 100644 --- a/cmake/pinmame32/CMakeLists_win-x64.txt +++ b/cmake/pinmame32/CMakeLists_win-x64.txt @@ -449,6 +449,7 @@ add_executable(pinmame32 WIN32 src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/pinmame32/CMakeLists_win-x86.txt b/cmake/pinmame32/CMakeLists_win-x86.txt index f20acab77..cf14a13cc 100644 --- a/cmake/pinmame32/CMakeLists_win-x86.txt +++ b/cmake/pinmame32/CMakeLists_win-x86.txt @@ -452,6 +452,7 @@ add_executable(pinmame32 WIN32 src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/vpinmame/CMakeLists_win-x64.txt b/cmake/vpinmame/CMakeLists_win-x64.txt index a124e821c..5e5e6a9d3 100644 --- a/cmake/vpinmame/CMakeLists_win-x64.txt +++ b/cmake/vpinmame/CMakeLists_win-x64.txt @@ -450,6 +450,7 @@ add_library(vpinmame SHARED src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/vpinmame/CMakeLists_win-x86.txt b/cmake/vpinmame/CMakeLists_win-x86.txt index f5d7d623f..87806e0b9 100644 --- a/cmake/vpinmame/CMakeLists_win-x86.txt +++ b/cmake/vpinmame/CMakeLists_win-x86.txt @@ -453,6 +453,7 @@ add_library(vpinmame SHARED src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/xpinmame/CMakeLists_linux-x64.txt b/cmake/xpinmame/CMakeLists_linux-x64.txt index a8246f5f2..32f1c610d 100644 --- a/cmake/xpinmame/CMakeLists_linux-x64.txt +++ b/cmake/xpinmame/CMakeLists_linux-x64.txt @@ -463,6 +463,7 @@ add_executable(xpinmame src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/cmake/xpinmame/CMakeLists_osx-x64.txt b/cmake/xpinmame/CMakeLists_osx-x64.txt index f569b2201..ccaf1257c 100644 --- a/cmake/xpinmame/CMakeLists_osx-x64.txt +++ b/cmake/xpinmame/CMakeLists_osx-x64.txt @@ -453,6 +453,7 @@ add_executable(xpinmame src/wpc/peyper.c src/wpc/peyper.h src/wpc/peypergames.c + src/libpinmame/pinmamedef.h src/wpc/play.c src/wpc/play.h src/wpc/playgames.c diff --git a/src/libpinmame/libpinmame.cpp b/src/libpinmame/libpinmame.cpp index 283cbac6b..2d7589cf6 100644 --- a/src/libpinmame/libpinmame.cpp +++ b/src/libpinmame/libpinmame.cpp @@ -654,7 +654,7 @@ int StartGame(const int gameNum) * PinmameGetGame ******************************************************/ -PINMAMEAPI PINMAME_STATUS PinmameGetGame(const char* const p_name, PinmameGameCallback callback, const void* p_userData) +PINMAMEAPI PINMAME_STATUS PinmameGetGame(const char* const p_name, PinmameGameCallback callback, void* const p_userData) { if (!_p_Config) return PINMAME_STATUS_CONFIG_NOT_SET; @@ -686,7 +686,7 @@ PINMAMEAPI PINMAME_STATUS PinmameGetGame(const char* const p_name, PinmameGameCa * PinmameGetGames ******************************************************/ -PINMAMEAPI PINMAME_STATUS PinmameGetGames(PinmameGameCallback callback, const void* p_userData) +PINMAMEAPI PINMAME_STATUS PinmameGetGames(PinmameGameCallback callback, void* const p_userData) { if (!_p_Config) return PINMAME_STATUS_CONFIG_NOT_SET; @@ -886,6 +886,8 @@ PINMAMEAPI PINMAME_STATUS PinmameRun(const char* const p_name) if (gameNum < 0) return PINMAME_STATUS_GAME_NOT_FOUND; + OnStateChange(2); // Starting state + vp_init(); _p_gameThread = new std::thread(StartGame, gameNum); @@ -1407,7 +1409,25 @@ PINMAMEAPI int PinmameGetChangedNVRAM(PinmameNVRAMState* const p_nvramStates) * PinmameSetUserData ******************************************************/ -PINMAMEAPI void PinmameSetUserData(const void* p_userData) +PINMAMEAPI void PinmameSetUserData(void* const p_userData) { _p_userData = (void*)p_userData; -} \ No newline at end of file +} + +/****************************************************** + * PinmameGetStateBlock + ******************************************************/ + +PINMAMEAPI int PinmameGetStateBlock(const unsigned int updateMask, pinmame_tMachineOutputState** pp_outputState) +{ + if (!_isRunning) + return -1; + + pinmame_tMachineOutputState* p_outputState = (pinmame_tMachineOutputState*)core_getOutputState(updateMask); + if (!p_outputState) + return -1; + + *pp_outputState = p_outputState; + + return 0; +} diff --git a/src/libpinmame/libpinmame.h b/src/libpinmame/libpinmame.h index 33aa951e9..30fa852e8 100644 --- a/src/libpinmame/libpinmame.h +++ b/src/libpinmame/libpinmame.h @@ -17,6 +17,10 @@ #define PINMAME_MAX_MECHSW 20 #define PINMAME_ACCUMULATOR_SAMPLES 8192 // from mixer.c +#define UINT32 uint32_t +#define UINT8 uint8_t +#include "pinmamedef.h" + typedef enum { PINMAME_LOG_LEVEL_DEBUG = 0, PINMAME_LOG_LEVEL_INFO = 1, @@ -394,19 +398,19 @@ typedef struct { unsigned int standardcode; } PinmameKeyboardInfo; -typedef void (PINMAMECALLBACK *PinmameGameCallback)(PinmameGame* p_game, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnStateUpdatedCallback)(int state, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnDisplayAvailableCallback)(int index, int displayCount, PinmameDisplayLayout* p_displayLayout, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnDisplayUpdatedCallback)(int index, void* p_displayData, PinmameDisplayLayout* p_displayLayout, const void* p_userData); -typedef int (PINMAMECALLBACK *PinmameOnAudioAvailableCallback)(PinmameAudioInfo* p_audioInfo, const void* p_userData); -typedef int (PINMAMECALLBACK *PinmameOnAudioUpdatedCallback)(void* p_buffer, int samples, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnMechAvailableCallback)(int mechNo, PinmameMechInfo* p_mechInfo, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnMechUpdatedCallback)(int mechNo, PinmameMechInfo* p_mechInfo, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnSolenoidUpdatedCallback)(PinmameSolenoidState* p_solenoidState, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnConsoleDataUpdatedCallback)(void* p_data, int size, const void* p_userData); -typedef int (PINMAMECALLBACK *PinmameIsKeyPressedFunction)(PINMAME_KEYCODE keycode, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnLogMessageCallback)(PINMAME_LOG_LEVEL logLevel, const char* format, va_list args, const void* p_userData); -typedef void (PINMAMECALLBACK *PinmameOnSoundCommandCallback)(int boardNo, int cmd, const void* p_userData); +typedef void (PINMAMECALLBACK *PinmameGameCallback)(PinmameGame* p_game, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnStateUpdatedCallback)(int state, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnDisplayAvailableCallback)(int index, int displayCount, PinmameDisplayLayout* p_displayLayout, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnDisplayUpdatedCallback)(int index, void* p_displayData, PinmameDisplayLayout* p_displayLayout, void* const p_userData); +typedef int (PINMAMECALLBACK *PinmameOnAudioAvailableCallback)(PinmameAudioInfo* p_audioInfo, void* const p_userData); +typedef int (PINMAMECALLBACK *PinmameOnAudioUpdatedCallback)(void* p_buffer, int samples, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnMechAvailableCallback)(int mechNo, PinmameMechInfo* p_mechInfo, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnMechUpdatedCallback)(int mechNo, PinmameMechInfo* p_mechInfo, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnSolenoidUpdatedCallback)(PinmameSolenoidState* p_solenoidState, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnConsoleDataUpdatedCallback)(void* p_data, int size, void* const p_userData); +typedef int (PINMAMECALLBACK *PinmameIsKeyPressedFunction)(PINMAME_KEYCODE keycode, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnLogMessageCallback)(PINMAME_LOG_LEVEL logLevel, const char* format, va_list args, void* const p_userData); +typedef void (PINMAMECALLBACK *PinmameOnSoundCommandCallback)(int boardNo, int cmd, void* const p_userData); typedef struct { const PINMAME_AUDIO_FORMAT audioFormat; @@ -426,8 +430,8 @@ typedef struct { PinmameOnSoundCommandCallback cb_OnSoundCommand; } PinmameConfig; -PINMAMEAPI PINMAME_STATUS PinmameGetGame(const char* const p_name, PinmameGameCallback callback, const void* p_userData); -PINMAMEAPI PINMAME_STATUS PinmameGetGames(PinmameGameCallback callback, const void* p_userData); +PINMAMEAPI PINMAME_STATUS PinmameGetGame(const char* const p_name, PinmameGameCallback callback, void* const p_userData); +PINMAMEAPI PINMAME_STATUS PinmameGetGames(PinmameGameCallback callback, void* const p_userData); PINMAMEAPI void PinmameSetConfig(const PinmameConfig* const p_config); PINMAMEAPI void PinmameSetPath(const PINMAME_FILE_TYPE fileType, const char* const p_path); PINMAMEAPI int PinmameGetCheat(); @@ -476,4 +480,6 @@ PINMAMEAPI void PinmameSetDIP(const int dipBank, const int value); PINMAMEAPI int PinmameGetMaxNVRAM(); PINMAMEAPI int PinmameGetNVRAM(PinmameNVRAMState* const p_nvramStates); PINMAMEAPI int PinmameGetChangedNVRAM(PinmameNVRAMState* const p_nvramStates); -PINMAMEAPI void PinmameSetUserData(const void* p_userData); +PINMAMEAPI void PinmameSetUserData(void* const p_userData); +PINMAMEAPI int PinmameGetStateBlock(const unsigned int updateMask, pinmame_tMachineOutputState** pp_outputState); + diff --git a/src/libpinmame/pinmamedef.h b/src/libpinmame/pinmamedef.h new file mode 100644 index 000000000..c4ff80ec4 --- /dev/null +++ b/src/libpinmame/pinmamedef.h @@ -0,0 +1,170 @@ +#pragma once + +// This file contains definitions used inside PinMame and for external integration through libpinmame + + +/////////////////////////////////////////////////////////////////////////////// +// +// Complete machine output state, provided in a global state block +// +// 0 length array is a non standard extension used intentionally here. +// Corresponding warning #4200 is therefore disabled when needed. +// + +// Digital output instantaneous states + +#pragma warning(disable : 4200) +typedef struct +{ + double updateTimestamp; + unsigned int nOutputs; + UINT32 outputBitset[]; // Bitset array of nOutputs bits with their current binary state +} pinmame_tBinaryStates; + + +// Controlled device states + +typedef enum { + PINMAME_DEVICE_STATE_TYPE_CUSTOM, // Custom state defined by each driver (value maybe either binary of 0/1 or 0/255, or modulated between 0..255) + PINMAME_DEVICE_STATE_TYPE_BULB, // Bulb state defined by its relative luminance and average filament temperature + PINMAME_DEVICE_STATE_TYPE_LED, // LED state defined by its relative luminance + PINMAME_DEVICE_STATE_TYPE_SOLENOID, // Solenoid (not yet defined) + PINMAME_DEVICE_STATE_TYPE_STEPPER, // Stepper motor (not yet defined) +} pinmame_tDeviceCategory; + +typedef enum { + PINMAME_DEVICE_TYPE_UNDEFINED, // Hardware type identifier + PINMAME_DEVICE_TYPE_VFD_BLUE, // VFD used for segment display + PINMAME_DEVICE_TYPE_VFD_GREEN, // VFD used for segment display + PINMAME_DEVICE_TYPE_NEON_PLASMA, // Neon Plasma display (Panaplex, Burroughs,...) used for segment and dot matrix display + PINMAME_DEVICE_TYPE_LED_RED, // +} pinmame_tDeviceType; + +typedef struct +{ + pinmame_tDeviceCategory category; + pinmame_tDeviceType type; + unsigned int mapping; // Device mapping to a user understandable id (for example lamp matrix are usually numbered by row/column and not index) + union + { + // PINMAME_DEVICE_STATE_TYPE_CUSTOM + UINT8 customState; // Custom value, depending on each driver definition + + // PINMAME_DEVICE_STATE_TYPE_BULB + struct + { + 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 at its rating voltage after heating stabilization) + } bulb; + + // PINMAME_DEVICE_STATE_TYPE_LED + float ledLuminance; // relative luminance to bulb design (equals 1.f when LED is pulsed at its designed PWM) + + // PINMAME_DEVICE_STATE_TYPE_SOLENOID + // To be added in a later revision + + // PINMAME_DEVICE_STATE_TYPE_STEPPER + // To be added in a later revision + }; +} pinmame_tDeviceState; + +#pragma warning(disable : 4200) +typedef struct +{ + double updateTimestamp; + unsigned int nDevices; + unsigned int stateByteSize; // sizeof(pinmame_tDeviceState), to be used to access states since the size of a single state my avolve in future revisions + pinmame_tDeviceState states[]; // array of nDevices * stateByteSize with the current device states +} pinmame_tDeviceStates; + + +// Alphanumeric segment displays + +// Individual segment layouts inside a display (usually made of multiple elements) +typedef enum { + PINMAME_SEG_LAYOUT_7, // 7 segments + PINMAME_SEG_LAYOUT_7C, // 7 segments with comma + PINMAME_SEG_LAYOUT_7D, // 7 segments with dot + PINMAME_SEG_LAYOUT_9, // 9 segments + PINMAME_SEG_LAYOUT_9C, // 9 segments with comma + PINMAME_SEG_LAYOUT_14, // 14 segments + PINMAME_SEG_LAYOUT_14D, // 14 segments with dot + PINMAME_SEG_LAYOUT_14DC, // 14 segments with dot and comma + PINMAME_SEG_LAYOUT_16, // 16 segments +} pinmame_tSegElementType; + +typedef struct +{ + pinmame_tSegElementType type; // see PINMAME_SEG_LAYOUT_xxx + float luminance[16]; // relative luminance of each segment (from 7 to 16) +} pinmame_tAlphaSegmentState; + +typedef struct +{ + unsigned int nElements; // Number of elements (each element is composed of 7 to 16 segments) + pinmame_tDeviceType type; // Hardware reference (Panaplex, VFD, ...) +} pinmame_tAlphaDisplayDef; + +#pragma warning(disable : 4200) +typedef struct +{ + double updateTimestamp; + unsigned int nDisplays; // Number of displays (each display is composed of multiple elements) + pinmame_tAlphaDisplayDef displayDefs[]; // Definition of each display (number of elements, hardware type,...) + // pinmame_tAlphaSegmentState states[]; // State of each display element as a linear array +} pinmame_tAlphaStates; +#define PINMAME_STATE_BLOCK_FIRST_ALPHA_FRAME(pAlphaState) ((pinmame_tAlphaSegmentState*)((UINT8*)(pAlphaState) + sizeof(pinmame_tAlphaStates) + (pAlphaState->nDisplays) * sizeof(pinmame_tAlphaDisplayDef))) + + +// DMD and video displays + +typedef enum { + PINMAME_FRAME_FORMAT_LUM, // Linear luminance (for monochrome DMD) + PINMAME_FRAME_FORMAT_RGB, // sRGB (for video frame) + PINMAME_FRAME_FORMAT_BP2, // 2 bitplanes, only used to identify frames + PINMAME_FRAME_FORMAT_BP4 // 4 bitplanes, only used to identify frames +} pinmame_tFrameDataFormat; + +#pragma warning(disable : 4200) +typedef struct +{ + unsigned int structSize; // Struct size including header and frame data in bytes (for safe DMD/Display array iteration) + unsigned int displayId; // Unique Id, shared between render frame and raw frame used for frame identification + double updateTimestamp; + unsigned int width; + unsigned int height; + pinmame_tFrameDataFormat dataFormat; + unsigned int frameId; + UINT8 frameData[]; // The display frame data which size depends on width, height and data format +} pinmame_tFrameState; + +typedef struct +{ + unsigned int nDisplays; + // pinmame_tFrameState displays[]; // Array of nDisplays * pinmame_tFrameState (can't be directly declared since frame size is undefined) +} pinmame_tDisplayStates; +#define PINMAME_STATE_BLOCK_FIRST_DISPLAY_FRAME(pDisplayState) ((pinmame_tFrameState*)((UINT8*)(pDisplayState) + sizeof(pinmame_tDisplayStates))) +#define PINMAME_STATE_BLOCK_NEXT_DISPLAY_FRAME(pFrameState) ((pinmame_tFrameState*)((UINT8*)(pFrameState) + (pFrameState)->structSize)) + + +// Global output state block + +typedef struct +{ + unsigned int versionID; // Data block format version (current version + pinmame_tBinaryStates* controlledOutputBinaryStates; // Binary state (instantaneous state of controlled output) + pinmame_tDeviceStates* controlledDeviceStates; // Device state (state of the emulated device) + pinmame_tDeviceStates* lampMatrixStates; // State of emulated lamps powered from a strobed lamp matrix + pinmame_tAlphaStates* alphaDisplayStates; // State of alphanumeric segment displays + pinmame_tDisplayStates* displayStates; // State of displays and DMDs + pinmame_tDisplayStates* rawDMDStates; // Raw logic state of DMDs (stable view of the stae that can be used for frame identification) +} pinmame_tMachineOutputState; + +#define PINMAME_STATE_REQMASK_GPOUTPUT_BINARY_STATE 0x01 +#define PINMAME_STATE_REQMASK_GPOUTPUT_DEVICE_STATE 0x02 +#define PINMAME_STATE_REQMASK_LAMP_DEVICE_STATE 0x04 +#define PINMAME_STATE_REQMASK_ALPHA_DEVICE_STATE 0x08 +#define PINMAME_STATE_REQMASK_DISPLAY_STATE 0x10 +#define PINMAME_STATE_REQMASK_RAW_DMD_STATE 0x20 +#define PINMAME_STATE_REQMASK_ALL 0x3F + diff --git a/src/libpinmame/test.cpp b/src/libpinmame/test.cpp index 4555c334d..dffea106c 100644 --- a/src/libpinmame/test.cpp +++ b/src/libpinmame/test.cpp @@ -134,13 +134,13 @@ void DumpAlphanumeric(int index, UINT16* p_displayData, PinmameDisplayLayout* p_ printf("%s\n", output[row]); } -void PINMAMECALLBACK Game(PinmameGame* game, const void* p_userData) +void PINMAMECALLBACK Game(PinmameGame* game, void* const p_userData) { printf("Game(): name=%s, description=%s, manufacturer=%s, year=%s, flags=%lu, found=%d\n", game->name, game->description, game->manufacturer, game->year, (unsigned long)game->flags, game->found); } -void PINMAMECALLBACK OnStateUpdated(int state, const void* p_userData) +void PINMAMECALLBACK OnStateUpdated(int state, void* const p_userData) { printf("OnStateUpdated(): state=%d\n", state); @@ -161,7 +161,7 @@ void PINMAMECALLBACK OnStateUpdated(int state, const void* p_userData) PinmameSetMech(1, &mechConfig); } -void PINMAMECALLBACK OnDisplayAvailable(int index, int displayCount, PinmameDisplayLayout* p_displayLayout, const void* p_userData) +void PINMAMECALLBACK OnDisplayAvailable(int index, int displayCount, PinmameDisplayLayout* p_displayLayout, void* const p_userData) { printf("OnDisplayAvailable(): index=%d, displayCount=%d, type=%d, top=%d, left=%d, width=%d, height=%d, depth=%d, length=%d\n", index, @@ -175,7 +175,7 @@ void PINMAMECALLBACK OnDisplayAvailable(int index, int displayCount, PinmameDisp p_displayLayout->length); } -void PINMAMECALLBACK OnDisplayUpdated(int index, void* p_displayData, PinmameDisplayLayout* p_displayLayout, const void* p_userData) +void PINMAMECALLBACK OnDisplayUpdated(int index, void* p_displayData, PinmameDisplayLayout* p_displayLayout, void* const p_userData) { printf("OnDisplayUpdated(): index=%d, type=%d, top=%d, left=%d, width=%d, height=%d, depth=%d, length=%d\n", index, @@ -197,7 +197,7 @@ void PINMAMECALLBACK OnDisplayUpdated(int index, void* p_displayData, PinmameDis } } -int PINMAMECALLBACK OnAudioAvailable(PinmameAudioInfo* p_audioInfo, const void* p_userData) +int PINMAMECALLBACK OnAudioAvailable(PinmameAudioInfo* p_audioInfo, void* const p_userData) { printf("OnAudioAvailable(): format=%d, channels=%d, sampleRate=%.2f, framesPerSecond=%.2f, samplesPerFrame=%d, bufferSize=%d\n", p_audioInfo->format, @@ -209,17 +209,17 @@ int PINMAMECALLBACK OnAudioAvailable(PinmameAudioInfo* p_audioInfo, const void* return p_audioInfo->samplesPerFrame; } -int PINMAMECALLBACK OnAudioUpdated(void* p_buffer, int samples, const void* p_userData) +int PINMAMECALLBACK OnAudioUpdated(void* p_buffer, int samples, void* const p_userData) { return samples; } -void PINMAMECALLBACK OnSolenoidUpdated(PinmameSolenoidState* p_solenoidState, const void* p_userData) +void PINMAMECALLBACK OnSolenoidUpdated(PinmameSolenoidState* p_solenoidState, void* const p_userData) { printf("OnSolenoidUpdated: solenoid=%d, state=%d\n", p_solenoidState->solNo, p_solenoidState->state); } -void PINMAMECALLBACK OnMechAvailable(int mechNo, PinmameMechInfo* p_mechInfo, const void* p_userData) +void PINMAMECALLBACK OnMechAvailable(int mechNo, PinmameMechInfo* p_mechInfo, void* const p_userData) { printf("OnMechAvailable: mechNo=%d, type=%d, length=%d, steps=%d, pos=%d, speed=%d\n", mechNo, @@ -230,7 +230,7 @@ void PINMAMECALLBACK OnMechAvailable(int mechNo, PinmameMechInfo* p_mechInfo, co p_mechInfo->speed); } -void PINMAMECALLBACK OnMechUpdated(int mechNo, PinmameMechInfo* p_mechInfo, const void* p_userData) +void PINMAMECALLBACK OnMechUpdated(int mechNo, PinmameMechInfo* p_mechInfo, void* const p_userData) { printf("OnMechUpdated: mechNo=%d, type=%d, length=%d, steps=%d, pos=%d, speed=%d\n", mechNo, @@ -241,17 +241,17 @@ void PINMAMECALLBACK OnMechUpdated(int mechNo, PinmameMechInfo* p_mechInfo, cons p_mechInfo->speed); } -void PINMAMECALLBACK OnConsoleDataUpdated(void* p_data, int size, const void* p_userData) +void PINMAMECALLBACK OnConsoleDataUpdated(void* p_data, int size, void* const p_userData) { printf("OnConsoleDataUpdated: size=%d\n", size); } -int PINMAMECALLBACK IsKeyPressed(PINMAME_KEYCODE keycode, const void* p_userData) +int PINMAMECALLBACK IsKeyPressed(PINMAME_KEYCODE keycode, void* const p_userData) { return 0; } -void PINMAMECALLBACK OnLogMessage(PINMAME_LOG_LEVEL logLevel, const char* format, va_list args, const void* p_userData) +void PINMAMECALLBACK OnLogMessage(PINMAME_LOG_LEVEL logLevel, const char* format, va_list args, void* const p_userData) { char buffer[1024]; vsnprintf(buffer, sizeof(buffer), format, args); @@ -262,7 +262,7 @@ void PINMAMECALLBACK OnLogMessage(PINMAME_LOG_LEVEL logLevel, const char* format printf("ERROR: %s\n", buffer); } -void PINMAMECALLBACK OnSoundCommand(int boardNo, int cmd, const void* p_userData) +void PINMAMECALLBACK OnSoundCommand(int boardNo, int cmd, void* const p_userData) { printf("OnSoundCommand: boardNo=%d, cmd=%d\n", boardNo, cmd); } diff --git a/src/win32com/Controller.cpp b/src/win32com/Controller.cpp index b898894e7..d06fa3783 100644 --- a/src/win32com/Controller.cpp +++ b/src/win32com/Controller.cpp @@ -1734,6 +1734,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(PINMAME_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(0x3F)]*/ 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/ControllerDmdDevice.cpp b/src/win32com/ControllerDmdDevice.cpp index 3c8c97a38..d42e63fd1 100644 --- a/src/win32com/ControllerDmdDevice.cpp +++ b/src/win32com/ControllerDmdDevice.cpp @@ -51,8 +51,8 @@ typedef void(*DmdDev_Render_16_Shades_t)(UINT16 width, UINT16 height, UINT8* fra typedef void(*DmdDev_Render_4_Shades_t)(UINT16 width, UINT16 height, UINT8* frame); typedef void(*DmdDev_Render_16_Shades_with_Raw_t)(UINT16 width, UINT16 height, UINT8* frame, UINT32 noOfRawFrames, UINT8* rawbuffer); typedef void(*DmdDev_Render_4_Shades_with_Raw_t)(UINT16 width, UINT16 height, UINT8* frame, UINT32 noOfRawFrames, UINT8* rawbuffer); -typedef void(*DmdDev_Render_PM_Alphanumeric_Frame_t)(core_segOverallLayout_t layout, const UINT16* const seg_data, const UINT16* const seg_data2); -typedef void(*DmdDev_Render_PM_Alphanumeric_Dim_Frame_t)(core_segOverallLayout_t layout, const UINT16* const seg_data, const char* const seg_dim, const UINT16* const seg_data2); +typedef void(*DmdDev_Render_PM_Alphanumeric_Frame_t)(core_tSegOverallLayout layout, const UINT16* const seg_data, const UINT16* const seg_data2); +typedef void(*DmdDev_Render_PM_Alphanumeric_Dim_Frame_t)(core_tSegOverallLayout layout, const UINT16* const seg_data, const char* const seg_dim, const UINT16* const seg_data2); typedef void(*DmdDev_Render_Lum_And_Raw_t)(UINT16 width, UINT16 height, UINT8* lumFrame, UINT8* rawFrame, UINT32 noOfRawFrames, UINT8* rawbuffer); typedef struct { @@ -239,7 +239,7 @@ extern "C" void dmddeviceRenderDMDFrame(const int width, const int height, UINT8 } } -extern "C" void dmddeviceRenderAlphanumericFrame(core_segOverallLayout_t layout, UINT16* seg_data, UINT16* seg_data2, char* seg_dim) { +extern "C" void dmddeviceRenderAlphanumericFrame(core_tSegOverallLayout layout, UINT16* seg_data, UINT16* seg_data2, char* seg_dim) { for (int i = 0; i < 2; i++) { if (dmdDevices[i].Render_PM_Alphanumeric_Dim_Frame) diff --git a/src/win32com/VPinMAME.idl b/src/win32com/VPinMAME.idl index 89e38553f..c70fe11cc 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(0x3F)] unsigned int updateMask); }; // WSHDlg and related interfaces diff --git a/src/wpc/byvidpin.c b/src/wpc/byvidpin.c index 51135519e..2e4d6ec7e 100644 --- a/src/wpc/byvidpin.c +++ b/src/wpc/byvidpin.c @@ -459,6 +459,12 @@ static VIDEO_STOP(byVP) { static PINMAME_VIDEO_UPDATE(byVP_update) { TMS9928A_refresh((core_gameData->hw.display ? 2 : 1), bitmap, 1); + struct rectangle bounds; + bounds.min_x = 0; + bounds.min_y = 0; + bounds.max_x = 192; + bounds.max_y = 256; + core_display_video_update(bitmap, &bounds, layout, 1); return 0; } diff --git a/src/wpc/core.c b/src/wpc/core.c index f1b559758..d88d525fb 100644 --- a/src/wpc/core.c +++ b/src/wpc/core.c @@ -107,6 +107,9 @@ void vp_setDIP(int bank, int value) { } #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) { @@ -681,7 +684,7 @@ static const tSegData segData[2][18] = {{ {20,15,&segSize1C[2][0]},/* SEG10 */ {20,15,&segSize1[2][0]}, /* SEG9 */ {20,15,&segSize1C[1][0]},/* SEG8 */ - {20,15,&segSize1C[4][0]},/* SEG8FD */ + {20,15,&segSize1C[4][0]},/* SEG8D */ {20,15,&segSize1[1][0]}, /* SEG7 */ {20,15,&segSize1C[1][0]},/* SEG87 */ {20,15,&segSize1C[1][0]},/* SEG87F */ @@ -732,7 +735,26 @@ static struct { UINT8 lastLampMatrix[CORE_MAXLAMPCOL]; int lastGI[CORE_MAXGI]; UINT64 lastSol; + /*-- Global output state block --*/ + pinmame_tMachineOutputState* 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; + pinmame_tFrameState* frame; + pinmame_tFrameState* raw; + } displayStateBlocks[32]; + int nSortedSegLayout; // Amount of alphanumeric segment display + core_tLCDLayout sortedSegLayout[CORE_SEGCOUNT]; // Sorted individual segment element, each display, shares the same top value + 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 /*-- VPinMAME specifics --*/ + #if defined(VPINMAME) + HANDLE outputStateSharedMem; // handle to the shared memory block used to share output states + #endif #if defined(VPINMAME) || defined(LIBPINMAME) UINT8 vpm_dmd_last_lum[DMD_MAXY * DMD_MAXX]; UINT8 vpm_dmd_luminance_lut[256]; @@ -910,14 +932,14 @@ void core_dmd_capture_frame(const int width, const int height, const UINT8* cons // Note that this part of the header is not used externally of VPinMAME (move it to something like core_dmdevice.h/core_dmddevice.c ?) extern int dmddeviceInit(const char* GameName, UINT64 HardwareGeneration, const tPMoptions* Options); extern void dmddeviceRenderDMDFrame(const int width, const int height, UINT8* dmdDotLum, UINT8* dmdDotRaw, UINT32 noOfRawFrames, UINT8* rawbuffer, const int isDMD2); -extern void dmddeviceRenderAlphanumericFrame(core_segOverallLayout_t layout, UINT16* seg_data, UINT16* seg_data2, char* seg_dim); +extern void dmddeviceRenderAlphanumericFrame(core_tSegOverallLayout layout, UINT16* seg_data, UINT16* seg_data2, char* seg_dim); extern void dmddeviceFwdConsoleData(UINT8 data); extern void dmddeviceDeInit(void); #endif /* VPINMAME */ -core_segOverallLayout_t layoutAlphanumericFrame(UINT64 gen, UINT8 total_disp, UINT8 *disp_num_segs, const char* GameName) { +core_tSegOverallLayout layoutAlphanumericFrame(UINT64 gen, UINT8 total_disp, UINT8 *disp_num_segs, const char* GameName) { // TODO this should be moved to the driver's definition, or MACHINE_INIT, setting it once and for all at machine startup from core_gameData->lcdLayout - core_segOverallLayout_t layout = CORE_SEGLAYOUT_None; + core_tSegOverallLayout layout = CORE_SEGLAYOUT_None; // switch to current game tech switch(gen){ @@ -1234,7 +1256,7 @@ static void updateDisplay(struct mame_bitmap *bitmap, const struct rectangle *cl if(!has_DMD_Video) { // Identify alphaseg layout - const core_segOverallLayout_t alpha_layout = layoutAlphanumericFrame(core_gameData->gen, n_seg_layouts, disp_num_segs, Machine->gamedrv->name); + const core_tSegOverallLayout alpha_layout = layoutAlphanumericFrame(core_gameData->gen, n_seg_layouts, disp_num_segs, Machine->gamedrv->name); assert(alpha_layout != CORE_SEGLAYOUT_Invalid); // Port of legacy hack that would call updateDisplay twice, once with the default display (4x7 and 2x2) then once for the additional display (3x2) @@ -1995,6 +2017,552 @@ int core_getDip(int dipBank) { #endif /* VPINMAME */ } +/*------------------------------------------------- +/ Lazily create and update a mem block holding output state +/--------------------------------------------------*/ +void core_createStateBlock() +{ + assert(locals.outputStateBlock == NULL); + struct core_dispLayout* layout, * parent_layout; + + // Evaluate required data block size + const unsigned int headerSize = sizeof(pinmame_tMachineOutputState); + // Controlled output digital state (limited to the first 32 ones since the emulation core does not store the others) + const unsigned int nControlledOutput = MIN(32, coreGlobals.nGI + (coreGlobals.nSolenoids ? coreGlobals.nSolenoids : (CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol))); + const unsigned int controlledOutputBinarySize = sizeof(pinmame_tBinaryStates) + ((nControlledOutput + 31) >> 5) * 4; + // Controlled device + const unsigned int nControlledDevice = coreGlobals.nGI + (coreGlobals.nSolenoids ? coreGlobals.nSolenoids : (CORE_FIRSTCUSTSOL - 1 + core_gameData->hw.custSol)); + const unsigned int controlledOutputDeviceSize = sizeof(pinmame_tDeviceStates) + nControlledDevice * sizeof(pinmame_tDeviceState); + // Lamps + 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)); + const unsigned int lampMatrixDeviceSize = sizeof(pinmame_tDeviceStates) + nLamps * sizeof(pinmame_tDeviceState); + // DMD, video, raw DMD and alphanumeric segment displays + int nSegLayouts = 0; + core_tLCDLayout* segLayout[128] = { 0 }; + unsigned int displayStateSize = sizeof(pinmame_tDisplayStates); + unsigned int rawDisplayStateSize = sizeof(pinmame_tDisplayStates); + unsigned int alphaDisplayDeviceSize = sizeof(pinmame_tAlphaStates) + locals.nSortedSegLayout * sizeof(pinmame_tAlphaSegmentState); + for (layout = core_gameData->lcdLayout, parent_layout = NULL; 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); parent_layout = layout + 1; layout = layout->lptr - 1; break; + case CORE_DMD: // DMD displays and LED matrices (for example RBION,... search for CORE_NODISP to list them) + rawDisplayStateSize += sizeof(pinmame_tFrameState) + layout->length * layout->start * 1; + displayStateSize += sizeof(pinmame_tFrameState) + layout->length * layout->start * 1; + break; + case CORE_VIDEO: // Video display for games like Baby PacMan, frames are stored as RGB8 + displayStateSize += sizeof(pinmame_tFrameState) + layout->length * layout->start * 3; + break; + default: // Alphanumeric segment displays + alphaDisplayDeviceSize += layout->length * sizeof(pinmame_tAlphaSegmentState); + segLayout[nSegLayouts] = layout; + nSegLayouts++; + break; + } + } + // Layout declaration in drivers were originaly made for rendering and are (sadly) also used to unswizzle alphanum segment data. + // We need to interpret them to build back the displays list with their individual components (for example see Space Gambler or WPC). + // The CORE_SEG mask also mix segment layouts (number of segment, with/without dot & comma, ...) with segment addressing (which output + // drive the segment, is the segment driuven together with another segment, ...). The CORE_SEG mask can also include a comma every + // three digit information, and the CORE_SEGREV flag which indicates that the memory position is in reversed order. + // We resolve all these to simply expose physical layouts, with stable output order. To do so, we convert them to individual + // elements and group them based on their declaration order and render position, then process the additional flags at setup here, + // or when accessing data (for example, to process shared segment command). + locals.nSortedSegLayout = 0; + memset(locals.sortedSegLayout, 0, sizeof(locals.sortedSegLayout)); + for (int i = 0; i < nSegLayouts; i++) + { + // Split layout into individual components, converting type to the state block enum, eventually applying forced commas and reversed order + for (int j = 0; j < segLayout[i]->length; j++) + { + assert(locals.nSortedSegLayout < CORE_SEGCOUNT); + memcpy(&locals.sortedSegLayout[locals.nSortedSegLayout], segLayout[i], sizeof(core_tLCDLayout)); + int type; + bool isThousands = (j > 0) && (j % 3 == 0); + switch (locals.sortedSegLayout[locals.nSortedSegLayout].type & CORE_SEGALL) { + case CORE_SEG7: type = PINMAME_SEG_LAYOUT_7; break; + case CORE_SEG7S: type = PINMAME_SEG_LAYOUT_7; break; + case CORE_SEG7SC: type = PINMAME_SEG_LAYOUT_7; break; + case CORE_SEG87F: + type = isThousands ? PINMAME_SEG_LAYOUT_7C : PINMAME_SEG_LAYOUT_7; + if (!isThousands) + locals.sortedSegLayout[locals.nSortedSegLayout].type = CORE_SEG87 | (locals.sortedSegLayout[locals.nSortedSegLayout].type & ~CORE_SEGALL); + break; + case CORE_SEG87: type = isThousands ? PINMAME_SEG_LAYOUT_7C : PINMAME_SEG_LAYOUT_7; break; + case CORE_SEG8: type = PINMAME_SEG_LAYOUT_7C; break; + case CORE_SEG8D: type = PINMAME_SEG_LAYOUT_7D; break; + case CORE_SEG9: type = PINMAME_SEG_LAYOUT_9; break; + case CORE_SEG10: type = PINMAME_SEG_LAYOUT_9C; break; + case CORE_SEG98F: + type = isThousands ? PINMAME_SEG_LAYOUT_9C : PINMAME_SEG_LAYOUT_9; + if (!isThousands) + locals.sortedSegLayout[locals.nSortedSegLayout].type = CORE_SEG98 | (locals.sortedSegLayout[locals.nSortedSegLayout].type & ~CORE_SEGALL); + break; + case CORE_SEG98: type = isThousands ? PINMAME_SEG_LAYOUT_9C : PINMAME_SEG_LAYOUT_9; break; // (8 segment is 9 with single command for vertical center) + case CORE_SEG16N: type = PINMAME_SEG_LAYOUT_14; break; + case CORE_SEG16D: type = PINMAME_SEG_LAYOUT_14D; break; + case CORE_SEG16: type = PINMAME_SEG_LAYOUT_14DC; break; + case CORE_SEG16R: type = PINMAME_SEG_LAYOUT_14DC; break; + case CORE_SEG16S: type = PINMAME_SEG_LAYOUT_16; break; + } + locals.sortedSegLayout[locals.nSortedSegLayout].type = type | (segLayout[i]->type & (CORE_SEGHIBIT | CORE_SEGREV)); + locals.sortedSegLayout[locals.nSortedSegLayout].left += j * 2; + locals.sortedSegLayout[locals.nSortedSegLayout].length = 1; + if (locals.sortedSegLayout[locals.nSortedSegLayout].type & CORE_SEGREV) + locals.sortedSegLayout[locals.nSortedSegLayout].start = locals.sortedSegLayout[locals.nSortedSegLayout].start + segLayout[i]->length - 1 - j; + else + locals.sortedSegLayout[locals.nSortedSegLayout].start = locals.sortedSegLayout[locals.nSortedSegLayout].start + j; + locals.nSortedSegLayout++; + } + } + int segDisplayStart = 0, nSegDisplays = 0; + for (int i = 0; i < locals.nSortedSegLayout; i++) + { + if ((i == locals.nSortedSegLayout - 1) // Last element + || (locals.sortedSegLayout[i].top != locals.sortedSegLayout[i + 1].top) // Next element is on another line + || (locals.sortedSegLayout[i].left + 2 != locals.sortedSegLayout[i + 1].left)) // There is gap before next element + // end of block could also be a change of element size (based on element type) but does not seems to be needed + { + for (int j = segDisplayStart; j <= i; j++) + { + locals.sortedSegLayout[j].top = nSegDisplays; + locals.sortedSegLayout[j].left = j - segDisplayStart; + locals.sortedSegLayout[j].length = i + 1 - segDisplayStart; + } + nSegDisplays++; + segDisplayStart = i + 1; + } + } + alphaDisplayDeviceSize += nSegDisplays * sizeof(pinmame_tAlphaDisplayDef); + // Final machine output state size + const unsigned int size = + headerSize + + controlledOutputBinarySize + + controlledOutputDeviceSize + + lampMatrixDeviceSize + + alphaDisplayDeviceSize + + displayStateSize + + rawDisplayStateSize; + + + #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 = (pinmame_tMachineOutputState*)(sharedMem + sizeof(unsigned int)); + #else + locals.outputStateBlock = (pinmame_tMachineOutputState*)malloc(size); + #endif + UINT8* data = (UINT8*)locals.outputStateBlock; + memset(data, 0, size); + + // Overall header. The version is a data format identifier that clients must check before reading anything. + // It should be changed if and only if data structure changes prevents existing implementations to read it, therefore breaking backward compatibility + locals.outputStateBlock->versionID = 1; + data += headerSize; + + // Controlled outputs: all digital general purpose controlled outputs (usually wired to physical devices like solenoids, but also flashers, motors, child boards, GI,...) with a fairly messy mapping + locals.outputStateBlock->controlledOutputBinaryStates = (pinmame_tBinaryStates*)data; + locals.outputStateBlock->controlledOutputBinaryStates->nOutputs = nControlledOutput; + data += controlledOutputBinarySize; + + // Controlled devices: we directly map one digital output to one device (which is sometime wrong) + locals.outputStateBlock->controlledDeviceStates = (pinmame_tDeviceStates*)data; + locals.outputStateBlock->controlledDeviceStates->nDevices = nControlledDevice; + locals.outputStateBlock->controlledDeviceStates->stateByteSize = sizeof(pinmame_tDeviceState); + 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->controlledDeviceStates->nDevices; i++) + { + const int outputType = locals.controlledOutputMapping[i].type; + // FIXME we should take the opportunity to tidy up the overall mapping mess + // For example we could split per board outputs to avoid overlays which have lead to the existing map (i.e. splitting solenoids, CPU controlled flipper board, WPC GI, extension boards, ...) + locals.outputStateBlock->controlledDeviceStates->states[i].mapping = i; + if ((outputType & 5) == 1) // 'Legacy' Solenoid & GI + locals.outputStateBlock->controlledDeviceStates->states[i].category = PINMAME_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->controlledDeviceStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_CUSTOM; + else if (type >= CORE_MODOUT_BULB_44_6_3V_AC && type <= CORE_MODOUT_BULB_906_25V_DC_S11) + locals.outputStateBlock->controlledDeviceStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_BULB; + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->controlledDeviceStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_LED; + else + locals.outputStateBlock->controlledDeviceStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_CUSTOM; + } + } + + // Lamp matrix: strobed lamp matrix (but some drivers also uses this for additional lamps/outputs) + locals.outputStateBlock->lampMatrixStates = (pinmame_tDeviceStates*)data; + locals.outputStateBlock->lampMatrixStates->nDevices = nLamps; + locals.outputStateBlock->lampMatrixStates->stateByteSize = sizeof(pinmame_tDeviceState); + for (int i = 0; i < nLamps; i++) + { + locals.outputStateBlock->lampMatrixStates->states[i].mapping = coreData->m2lamp ? coreData->m2lamp((i / 8) + 1, i & 7) : i; + 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->lampMatrixStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_BULB; + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->lampMatrixStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_LED; + else + locals.outputStateBlock->lampMatrixStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_CUSTOM; + } + else + locals.outputStateBlock->lampMatrixStates->states[i].category = PINMAME_DEVICE_STATE_TYPE_CUSTOM; + } + data += lampMatrixDeviceSize; + + // Alphanumeric segment displays + locals.outputStateBlock->alphaDisplayStates = (pinmame_tAlphaStates*)data; + locals.outputStateBlock->alphaDisplayStates->nDisplays = nSegDisplays; + pinmame_tAlphaSegmentState* alphaStates = PINMAME_STATE_BLOCK_FIRST_ALPHA_FRAME(locals.outputStateBlock->alphaDisplayStates); + for (int i = 0; i < locals.nSortedSegLayout; i++) + { + alphaStates[i].type = locals.sortedSegLayout[i].type; + locals.outputStateBlock->alphaDisplayStates->displayDefs[locals.sortedSegLayout[i].top].nElements = locals.sortedSegLayout[i].length; + } + // TODO define more hardware hints + if (core_gameData->gen & GEN_GTS3) + for (int i = 0; i < nSegDisplays; i++) + locals.outputStateBlock->alphaDisplayStates->displayDefs[i].type = + locals.outputStateBlock->alphaDisplayStates->displayDefs[i].nElements == 20 ? PINMAME_DEVICE_TYPE_VFD_GREEN : PINMAME_DEVICE_TYPE_LED_RED; + else if (core_gameData->gen & (GEN_WPCALPHA_1 | GEN_WPCALPHA_2)) + for (int i = 0; i < nSegDisplays; i++) + locals.outputStateBlock->alphaDisplayStates->displayDefs[i].type = PINMAME_DEVICE_TYPE_NEON_PLASMA; + data += alphaDisplayDeviceSize; + + // Displays: this includes main DMD but also small LED matrix display (WOF, WPT, RBION,...) and video displays (Caveman, MrGame machines,...) + locals.outputStateBlock->displayStates = (pinmame_tDisplayStates*)data; + locals.outputStateBlock->displayStates->nDisplays = 0; + pinmame_tFrameState* nextDisplayFrame = PINMAME_STATE_BLOCK_FIRST_DISPLAY_FRAME(locals.outputStateBlock->displayStates); + for (layout = core_gameData->lcdLayout, parent_layout = NULL; 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); 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->displayStates->nDisplays].layout = layout; + locals.displayStateBlocks[locals.outputStateBlock->displayStates->nDisplays].frame = nextDisplayFrame; + nextDisplayFrame->displayId = locals.outputStateBlock->displayStates->nDisplays; + nextDisplayFrame->width = layout->length; + nextDisplayFrame->height = layout->start; + if ((layout->type & CORE_SEGMASK) == CORE_VIDEO) { + nextDisplayFrame->dataFormat = PINMAME_FRAME_FORMAT_RGB; + nextDisplayFrame->structSize = sizeof(pinmame_tFrameState) + layout->length * layout->start * 3; + } + else { + nextDisplayFrame->dataFormat = PINMAME_FRAME_FORMAT_LUM; + nextDisplayFrame->structSize = sizeof(pinmame_tFrameState) + layout->length * layout->start; + } + nextDisplayFrame = PINMAME_STATE_BLOCK_NEXT_DISPLAY_FRAME(nextDisplayFrame); + assert(((UINT8*)nextDisplayFrame - (UINT8*)locals.outputStateBlock->displayStates) <= displayStateSize); + locals.outputStateBlock->displayStates->nDisplays++; + break; + default: break; // Alphanumeric segment displays + } + } + data = (UINT8*)nextDisplayFrame; + + // Raw DMD frames used for frame identification to perform colorization or identify game events + locals.outputStateBlock->rawDMDStates = (pinmame_tDisplayStates*)data; + locals.outputStateBlock->rawDMDStates->nDisplays = 0; + nextDisplayFrame = PINMAME_STATE_BLOCK_FIRST_DISPLAY_FRAME(locals.outputStateBlock->rawDMDStates); + int dspIndex = 0; + for (layout = core_gameData->lcdLayout, parent_layout = NULL; 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); parent_layout = layout + 1; layout = layout->lptr - 1; break; + case CORE_VIDEO: // We do not provide raw frame for video output but we match displayId between raw and render frame + dspIndex++; + break; + case CORE_DMD: // Raw frame for DMD displays is a combination of the last PWM binary frames + locals.displayStateBlocks[dspIndex].raw = nextDisplayFrame; + nextDisplayFrame->displayId = dspIndex; + nextDisplayFrame->width = layout->length; + nextDisplayFrame->height = layout->start; + nextDisplayFrame->dataFormat = ((core_gameData->gen & (GEN_SAM | GEN_SPA | GEN_ALVG_DMD2)) || (strncasecmp(Machine->gamedrv->name, "smb", 3) == 0) || (strncasecmp(Machine->gamedrv->name, "cueball", 7) == 0)) ? PINMAME_FRAME_FORMAT_BP4 : PINMAME_FRAME_FORMAT_BP2; + nextDisplayFrame->structSize = sizeof(pinmame_tFrameState) + layout->length * layout->start; + nextDisplayFrame = PINMAME_STATE_BLOCK_NEXT_DISPLAY_FRAME(nextDisplayFrame); + assert(((UINT8*)nextDisplayFrame - (UINT8*)locals.outputStateBlock->rawDMDStates) <= displayStateSize); + locals.outputStateBlock->rawDMDStates->nDisplays++; + dspIndex++; + break; + default: break; // Alphanumeric segment displays + } + } + data = (UINT8*)nextDisplayFrame; +} + +pinmame_tMachineOutputState* 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 & PINMAME_STATE_REQMASK_GPOUTPUT_BINARY_STATE) + { + locals.outputStateBlock->controlledOutputBinaryStates->updateTimestamp = now; + locals.outputStateBlock->controlledOutputBinaryStates->outputBitset[0] = 0; + for (unsigned int i = 0; i < locals.outputStateBlock->controlledOutputBinaryStates->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->controlledOutputBinaryStates->outputBitset[0] |= 1 << i; + } + } + + if (updateMask & PINMAME_STATE_REQMASK_GPOUTPUT_DEVICE_STATE) + { + core_update_pwm_solenoids(); + core_update_pwm_gis(); + locals.outputStateBlock->controlledDeviceStates->updateTimestamp = now; + for (unsigned int i = 0; i < locals.outputStateBlock->controlledDeviceStates->nDevices; i++) + { + switch (locals.controlledOutputMapping[i].type) + { + case 1: // Solenoid using core_getSol + locals.outputStateBlock->controlledDeviceStates->states[i].customState = core_getSol(locals.controlledOutputMapping[i].solIndex) ? 1 : 0; + break; + case 3: // GI using coreGlobals.gi + locals.outputStateBlock->controlledDeviceStates->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->controlledDeviceStates->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->controlledDeviceStates->states[i].bulb.luminance = coreGlobals.physicOutputState[output].value; + locals.outputStateBlock->controlledDeviceStates->states[i].bulb.filamentTemperature = 0.f; // Not yet implemented + } + else if (type >= CORE_MODOUT_LED && type <= CORE_MODOUT_VFD_STROBE_1_16MS) + locals.outputStateBlock->controlledDeviceStates->states[i].ledLuminance = coreGlobals.physicOutputState[output].value; + else + locals.outputStateBlock->controlledDeviceStates->states[i].customState = saturatedByte(coreGlobals.physicOutputState[output].value); + break; + } + default: // Invalid mapping + assert(0); + break; + } + } + } + + if (updateMask & PINMAME_STATE_REQMASK_LAMP_DEVICE_STATE) + { + core_update_pwm_lamps(); + pinmame_tDeviceStates* lampMatrix = locals.outputStateBlock->lampMatrixStates; + lampMatrix->updateTimestamp = now; + if (coreGlobals.nLamps && (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_LAMPS))) + { + for (unsigned int i = 0; i < lampMatrix->nDevices; i++) + { + switch (lampMatrix->states[i].category) + { + case PINMAME_DEVICE_STATE_TYPE_BULB: lampMatrix->states[i].bulb.luminance = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value; break; + case PINMAME_DEVICE_STATE_TYPE_LED: lampMatrix->states[i].ledLuminance = coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value; break; + case PINMAME_DEVICE_STATE_TYPE_CUSTOM: lampMatrix->states[i].customState = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value); break; + } + } + } + else + { + // Backward compatibility for modulated LED & RGB LEDs of SAM hardware: 2 regular columns, then modulated LEDs + 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) + lampMatrix->states[8 * col + row].customState = rowLamp & 0x01; + if (hasSAMModulatedLeds) + for (unsigned int i = 80; i < (unsigned int)coreGlobals.nLamps; i++) + lampMatrix->states[i].customState = saturatedByte(coreGlobals.physicOutputState[CORE_MODOUT_LAMP0 + i].value); + } + } + + if (updateMask & PINMAME_STATE_REQMASK_ALPHA_DEVICE_STATE) + { + if (coreGlobals.nAlphaSegs) // Force update as we always return PWM value if available + core_update_pwm_outputs(CORE_MODOUT_SEG0, coreGlobals.nAlphaSegs); + pinmame_tAlphaStates* alphaDisplay = locals.outputStateBlock->alphaDisplayStates; + alphaDisplay->updateTimestamp = now; + static int nSegments[] = { 7, 8, 8, 9, 10, 14, 15, 16, 16 }; + int segDisplayStart = 0; + pinmame_tAlphaSegmentState* alphaStates = PINMAME_STATE_BLOCK_FIRST_ALPHA_FRAME(alphaDisplay); + for (int i = 0; i < locals.nSortedSegLayout; i++) + { + const core_tLCDLayout layout = locals.sortedSegLayout[i]; + const int type = MIN(layout.type & CORE_SEGALL, 9); + const int nSegs = nSegments[type]; + if (coreGlobals.nAlphaSegs) // Always return modulated value if available + { + int pos = CORE_MODOUT_SEG0 + layout.start * 16; + if (layout.type & CORE_SEGHIBIT) pos += 8; + for (int j = 0; j < nSegs; j++, pos++) // Loop over each segments of the current character (up to 16) + alphaStates[i].luminance[j] = coreGlobals.physicOutputState[pos].value; + } + else + { + UINT16 segs = coreGlobals.segments[layout.start].w; + if (layout.type & CORE_SEGHIBIT) segs >>= 8; + for (int j = 0; j < nSegs; j++, segs >>= 1) // Loop over each segments of the current character (up to 16) + alphaStates[i].luminance[j] = (segs & 1) ? 1.f : 0.f; + } + if ((type == CORE_SEG9) || (type == CORE_SEG98) || (type == CORE_SEG98F)) { + // Bottom half of vertical center is controlled by upper half + alphaStates[i].luminance[9] = alphaStates[i].luminance[8]; + } + if (type == CORE_SEG16R) { + // Reverse comma / dot + UINT8 v = alphaStates[i].luminance[15]; + alphaStates[i].luminance[15] = alphaStates[i].luminance[7]; + alphaStates[i].luminance[7] = v; + } + if ((type == CORE_SEG98F) || (type == CORE_SEG87F)) { + // Comma is lit if at least one segment is on + for (int j = 0; j < nSegs; j++) + if (j != 8 && alphaStates[i].luminance[j] > alphaStates[i].luminance[8]) + alphaStates[i].luminance[8] = alphaStates[i].luminance[j]; + } + } + } + + // Nothing to do for Video, DMD and LED matrix as they are directly updated in core_dmd_video_update (to get the raw data instead of rendered one, needed for correct shading and coloring plugins) + // TODO we should perform DMD update here, on request, to fix animation stuterring (for the time being, we update at 60Hz while the emulated DMD has a higher refresh rate and the user + // display can have any display rate, so we end up creating stutters), but this design would need DMD update to be thread safe as core_getOutputState can be called from another thread than + // the emulation thread. + /*if (updateMask & PINMAME_STATE_REQMASK_DISPLAY_STATE) + { + for (unsigned int i = 0; i < locals.outputStateBlock->displayStates->nDisplays; i++) + { + struct core_dispLayout* layout = locals.displayStateBlocks[i].layout; + pinmame_tFrameState* frame = locals.displayStateBlocks[i].frame; + frame->updateTimestamp = now; + if (layout->fptr && (layout->type & CORE_SEGALL) == CORE_DMD) + { + } + else if (layout->fptr && (layout->type & CORE_SEGALL) == CORE_VIDEO) + { + } + } + }*/ + + return locals.outputStateBlock; +} + /*-------------------- / Draw a LED digit /---------------------*/ @@ -2191,6 +2759,9 @@ static MACHINE_INIT(core) { // DMD USB Init if(g_fShowPinDMD && !time_to_reset) dmddeviceInit(g_szGameName, core_gameData->gen, &pmoptions); + + locals.outputStateBlock = NULL; + locals.outputStateSharedMem = INVALID_HANDLE_VALUE; #endif /*-- Generate LUTs for VPinMAME DMD --*/ @@ -2258,7 +2829,22 @@ static MACHINE_STOP(core) { #ifdef VPINMAME // DMD USB Kill if(g_fShowPinDMD && !time_to_reset) - dmddeviceDeInit(); + dmddeviceDeInit(); + + 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) g_raw_dmdx = ~0u; @@ -3381,6 +3967,32 @@ void core_dmd_render_dmddevice(const int width, const int height, const UINT8* c } #endif +// Prepare data for VPinMame state block +#if defined(VPINMAME) || defined(LIBPINMAME) +void core_dmd_update_state_block(const struct core_dispLayout* layout, const UINT8* dmdDotRaw, const UINT8* dmdDotLum) { + if (locals.outputStateBlock && locals.outputStateBlock->displayStates) { + for (unsigned int i = 0; i < locals.outputStateBlock->displayStates->nDisplays; i++) { + if (locals.displayStateBlocks[i].layout == layout) { + const unsigned int size = layout->start * layout->length; + locals.displayStateBlocks[i].frame->updateTimestamp = timer_get_time(); + if (memcmp(locals.displayStateBlocks[i].frame->frameData, dmdDotLum, size) != 0) + { + memcpy(locals.displayStateBlocks[i].frame->frameData, dmdDotLum, size); + locals.displayStateBlocks[i].frame->frameId++; + } + locals.displayStateBlocks[i].raw->updateTimestamp = timer_get_time(); + if (memcmp(locals.displayStateBlocks[i].raw->frameData, dmdDotRaw, size) != 0) + { + memcpy(locals.displayStateBlocks[i].raw->frameData, dmdDotRaw, size); + locals.displayStateBlocks[i].raw->frameId++; + } + return; + } + } + } +} +#endif + // Save main DMD bitplane and raw frames to a capture file // DMD frame capture can be enabled either by: // - setting g_fDumpFrames (not supported as it is only available through keyboard input which VPinMame doesn't have) @@ -3483,7 +4095,7 @@ void core_dmd_video_update(struct mame_bitmap *bitmap, const struct rectangle *c core_dmd_render_lpm(layout->length, layout->start, dmdDotLum, dmdDotRaw); has_DMD_Video = 1; } - + core_dmd_update_state_block(layout, dmdDotRaw, dmdDotLum); #elif defined(VPINMAME) const int isMainDMD = layout->length >= 128; // Up to 2 main DMDs (1 for all games, except Strikes N' Spares which has 2) // FIXME check for VPinMame window hidden/shown state, and do not render if hidden @@ -3494,10 +4106,57 @@ void core_dmd_video_update(struct mame_bitmap *bitmap, const struct rectangle *c core_dmd_render_dmddevice(layout->length, layout->start, dmdDotLum, dmdDotRaw, layout->top != 0); core_dmd_capture_frame(layout->length, layout->start, dmdDotRaw, raw_dmd_frame_count ,raw_dmd_frames); } - + core_dmd_update_state_block(layout, dmdDotRaw, dmdDotLum); #elif defined(PINMAME) core_dmd_render_internal(bitmap, layout->left, layout->top, layout->length, layout->start, dmdDotLum, pmoptions.dmd_antialias && !(layout->type & CORE_DMDNOAA)); + #endif +} +void core_display_video_update(struct mame_bitmap* bitmap, const struct rectangle* cliprect, const struct core_dispLayout* layout, const int rotation) { + #if defined(VPINMAME) + if (locals.outputStateBlock && locals.outputStateBlock->displayStates) { + for (unsigned int i = 0; i < locals.outputStateBlock->displayStates->nDisplays; i++) { + if (locals.displayStateBlocks[i].layout == layout) { + const unsigned int size = layout->start * layout->length; + locals.displayStateBlocks[i].frame->updateTimestamp = timer_get_time(); + locals.displayStateBlocks[i].frame->frameId++; + switch (rotation) + { + case 0: + locals.displayStateBlocks[i].frame->width = layout->length; + locals.displayStateBlocks[i].frame->height = layout->start; + break; + case 1: + locals.displayStateBlocks[i].frame->width = layout->start; + locals.displayStateBlocks[i].frame->height = layout->length; + break; + case 3: + locals.displayStateBlocks[i].frame->width = layout->start; + locals.displayStateBlocks[i].frame->height = layout->length; + break; + default: + assert(FALSE); + break; + } + for (int y = 0; y < layout->start; y++) { + for (int x = 0; x < layout->length; x++) { + int pos; + switch (rotation) + { + case 0: pos = (x + y * layout->length) * 3; break; + case 1: pos = ((layout->start - 1 - y) + x * layout->start) * 3; break; + case 2: pos = (y + (layout->length - 1 - x) * layout->start) * 3; break; + } + palette_get_color(bitmap->read(bitmap, x, y), + &locals.displayStateBlocks[i].frame->frameData[pos ], + &locals.displayStateBlocks[i].frame->frameData[pos + 1], + &locals.displayStateBlocks[i].frame->frameData[pos + 2]); + } + } + break; + } + } + } #endif } diff --git a/src/wpc/core.h b/src/wpc/core.h index 401134264..76582f27e 100644 --- a/src/wpc/core.h +++ b/src/wpc/core.h @@ -10,6 +10,10 @@ #include "gen.h" #include "sim.h" +#include "drawgfx.h" + +#include "../libpinmame/pinmamedef.h" + /*-- some convenience macros --*/ #ifndef FALSE #define FALSE (0) @@ -172,24 +176,24 @@ / Generic Display layout data /------------------------------*/ /* The different kind of display units */ -#define CORE_SEG16 0 // 16 segments -#define CORE_SEG16R 1 // 16 segments with comma and period reversed -#define CORE_SEG10 2 // 9 segments and comma -#define CORE_SEG9 3 // 9 segments -#define CORE_SEG8 4 // 7 segments and comma -#define CORE_SEG8D 5 // 7 segments and period -#define CORE_SEG7 6 // 7 segments -#define CORE_SEG87 7 // 7 segments, comma every three -#define CORE_SEG87F 8 // 7 segments, forced comma every three -#define CORE_SEG98 9 // 9 segments, comma every three -#define CORE_SEG98F 10 // 9 segments, forced comma every three -#define CORE_SEG7S 11 // 7 segments, small -#define CORE_SEG7SC 12 // 7 segments, small, with comma +#define CORE_SEG16 0 // 14 segments with comma and period +#define CORE_SEG16R 1 // 14 segments with comma and period reversed +#define CORE_SEG10 2 // 9 segments and comma +#define CORE_SEG9 3 // 9 segments +#define CORE_SEG8 4 // 7 segments and comma +#define CORE_SEG8D 5 // 7 segments and period +#define CORE_SEG7 6 // 7 segments +#define CORE_SEG87 7 // 7 segments, comma every three +#define CORE_SEG87F 8 // 7 segments, forced comma every three +#define CORE_SEG98 9 // 9 segments, comma every three +#define CORE_SEG98F 10 // 9 segments, forced comma every three +#define CORE_SEG7S 11 // 7 segments, small +#define CORE_SEG7SC 12 // 7 segments, small, with comma #define CORE_SEG16S 13 // 16 segments with split top and bottom line #define CORE_DMD 14 // DMD Display #define CORE_VIDEO 15 // VIDEO Display -#define CORE_SEG16N 16 // 16 segments without commas -#define CORE_SEG16D 17 // 16 segments with periods only +#define CORE_SEG16N 16 // 14 segments (no dot nor comma) +#define CORE_SEG16D 17 // 14 segments with period but no comma #define CORE_SEGALL 0x1f // maximum segment definition number #define CORE_IMPORT 0x20 // Link to another display layout @@ -229,7 +233,7 @@ struct core_dispLayout { typedef struct core_dispLayout core_tLCDLayout, *core_ptLCDLayout; // Overall alphanumeric display layout. Used externally by VPinMame's dmddevice. Don't change order typedef enum { - CORE_SEGLAYOUT_None, + CORE_SEGLAYOUT_None = 0, CORE_SEGLAYOUT_2x16Alpha, CORE_SEGLAYOUT_2x20Alpha, CORE_SEGLAYOUT_2x7Alpha_2x7Num, @@ -247,7 +251,7 @@ typedef enum { CORE_SEGLAYOUT_1x7Num_1x16Alpha_1x16Num, CORE_SEGLAYOUT_1x16Alpha_1x16Num_1x7Num_1x4Num, CORE_SEGLAYOUT_Invalid -} core_segOverallLayout_t; +} core_tSegOverallLayout; #define PINMAME_VIDEO_UPDATE(name) int (name)(struct mame_bitmap *bitmap, const struct rectangle *cliprect, const struct core_dispLayout *layout) @@ -607,6 +611,9 @@ extern int core_getPulsedSol(int solNo); extern UINT64 core_getAllSol(void); extern void core_getAllPhysicSols(float* const state); +/*-- full output state --*/ +extern pinmame_tMachineOutputState* 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(void) { if (options.usemodsol & (CORE_MODOUT_FORCE_ON | CORE_MODOUT_ENABLE_PHYSOUT_GI)) core_update_pwm_outputs(CORE_MODOUT_GI0, coreGlobals.nGI); } @@ -669,6 +676,8 @@ extern void core_dmd_submit_frame(core_tDMDPWMState* dmd_state, const UINT8* fra extern void core_dmd_update_pwm(core_tDMDPWMState* dmd_state); extern void core_dmd_video_update(struct mame_bitmap *bitmap, const struct rectangle *cliprect, const struct core_dispLayout *layout, core_tDMDPWMState* dmd_state); +extern void core_display_video_update(struct mame_bitmap* bitmap, const struct rectangle* cliprect, const struct core_dispLayout* layout, const int rotation); + extern void core_sound_throttle_adj(int sIn, int *sOut, int buffersize, double samplerate); /*-- nvram handling --*/ diff --git a/src/wpc/sam.c b/src/wpc/sam.c index 4c0f21ac6..d037678fb 100644 --- a/src/wpc/sam.c +++ b/src/wpc/sam.c @@ -2252,7 +2252,9 @@ static PINMAME_VIDEO_UPDATE(samminidmd_update) { bits = (bits << 1) | (coreGlobals.dmdDotRaw[y * 5 + x] ? 1 : 0); coreGlobals.drawSeg[35 * dmd_y + 5 * dmd_x + x] = bits; } +#ifndef VPINMAME if (!pmoptions.dmd_only) +#endif core_dmd_video_update(bitmap, cliprect, layout, NULL); return 0; } @@ -2274,8 +2276,10 @@ static PINMAME_VIDEO_UPDATE(samminidmd2_update) { bits = (bits<<1) | (coreGlobals.dmdDotRaw[kk * layout->length + ii] ? 1 : 0); coreGlobals.drawSeg[ii] = bits; } - if (!pmoptions.dmd_only) - core_dmd_video_update(bitmap, cliprect, layout, NULL); +#ifndef VPINMAME + if (!pmoptions.dmd_only) +#endif + core_dmd_video_update(bitmap, cliprect, layout, NULL); return 0; } diff --git a/src/wpc/wpc.c b/src/wpc/wpc.c index 8e956c849..7f13678dc 100644 --- a/src/wpc/wpc.c +++ b/src/wpc/wpc.c @@ -793,16 +793,14 @@ WRITE_HANDLER(wpc_w) { { //static double prev; printf("WPC_ALPHA2LO %8.5fms %02x %02x\n", timer_get_time() - prev, wpc_data[WPC_ALPHAPOS], data); prev = timer_get_time(); wpclocals.alphaSeg[20+wpc_data[WPC_ALPHAPOS]].b.lo |= data; - if (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_ALPHASEGS | CORE_MODOUT_FORCE_ON)) - core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (20 + wpc_data[WPC_ALPHAPOS]) * 2 * 8, data); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + (20 + wpc_data[WPC_ALPHAPOS]) * 2 * 8, data); } break; case WPC_ALPHA2HI: if ((core_gameData->gen & GENWPC_HASDMD) == 0) { wpclocals.alphaSeg[20+wpc_data[WPC_ALPHAPOS]].b.hi |= data; - if (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_ALPHASEGS | CORE_MODOUT_FORCE_ON)) - core_write_pwm_output_8b(CORE_MODOUT_SEG0 + ((20 + wpc_data[WPC_ALPHAPOS]) * 2 + 1) * 8, data); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + ((20 + wpc_data[WPC_ALPHAPOS]) * 2 + 1) * 8, data); } break; case WPC_LAMPROW: /* row and column can be written in any order */ @@ -869,17 +867,14 @@ WRITE_HANDLER(wpc_w) { // The delay between setting segments then blanking them is used to a rough PWM dimming // Overall timing is 1ms maximum per digit over a 16ms period //static double prev; printf("WPC_ALPHAPOS %8.5fms %02x\n", timer_get_time() - prev, data); prev = timer_get_time(); - if (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_ALPHASEGS | CORE_MODOUT_FORCE_ON)) - { - int prevIndex = CORE_MODOUT_SEG0 + wpc_data[WPC_ALPHAPOS] * 2 * 8; - int newIndex = CORE_MODOUT_SEG0 + data * 2 * 8; - if (prevIndex != newIndex) - for (int i = 0; i < 4; i++) - { - int offst = i == 0 ? 0 : i == 1 ? 8 : i == 2 ? 320 : 328; - core_write_pwm_output_8b(newIndex + offst, coreGlobals.binaryOutputState[(prevIndex + offst) >> 3]); - core_write_pwm_output_8b(prevIndex + offst, 0); - } + int prevIndex = CORE_MODOUT_SEG0 + wpc_data[WPC_ALPHAPOS] * 2 * 8; + int newIndex = CORE_MODOUT_SEG0 + data * 2 * 8; + if (prevIndex != newIndex) { + for (int i = 0; i < 4; i++) { + int offst = i == 0 ? 0 : i == 1 ? 8 : i == 2 ? 320 : 328; + core_write_pwm_output_8b(newIndex + offst, coreGlobals.binaryOutputState[(prevIndex + offst) >> 3]); + core_write_pwm_output_8b(prevIndex + offst, 0); + } } } break; /* just save position */ @@ -896,8 +891,7 @@ WRITE_HANDLER(wpc_w) { else if ((core_gameData->gen & GENWPC_HASDMD) == 0) // WPC_ALPHA1LO { wpclocals.alphaSeg[wpc_data[WPC_ALPHAPOS]].b.lo |= data; - if (options.usemodsol & (CORE_MODOUT_ENABLE_PHYSOUT_ALPHASEGS | CORE_MODOUT_FORCE_ON)) - core_write_pwm_output_8b(CORE_MODOUT_SEG0 + wpc_data[WPC_ALPHAPOS] * 2 * 8, data); + core_write_pwm_output_8b(CORE_MODOUT_SEG0 + wpc_data[WPC_ALPHAPOS] * 2 * 8, data); } break; case WPC_EXTBOARD3: /* WPC_ALPHA1HI */