Hello TEAM :-)
I'm offering you a new version of MAME currently under development:
README – CRT-MAME 0.168 V1.3
Binary files and source code available here on GitHub:
https://github.com/HardCade/hardcade/releasesDevelopment Version – December 2025
© 2025 Hardcade – PlayRetro
===========================================================================================
PROJECT: CRT-MAME 0.168 Edition
CRT-MAME 0.168 is a specialized version of MAME 0.168 optimized for: 15 kHz CRT monitors (arcade, TV, PVM/BVM, Hantarex, Nanao, etc.) and LCD
monitors
Minimal input lag,
advanced and precise refresh rate control per game.
Objectives:
- Achieve minimal input lag
- Achieve increased display performance and superior behavior (compared to existing emulators) for 15 kHz CRTs.
- Achieve ultra-smooth display and optimal performance on LCD screens.
===============================================
CURRENT MAIN FEATURES (subject to change)
==============================================
- Saving the Refresh Rate slider to CFG files
- Improved slider (precision, stability, rounding, defect handling)
- Added Windows input timestamp (anti-lag preparation)
- Added Late Input Polling (reduces input lag by a full frame)
- Added Direct3D optimization – "Direct Present Minimal" (reduces display lag)
- Added Adaptive HardSync (stabilizes frame timing and prevents stuttering, reduces overall latency)
More features and improvements are coming soon...
===========================================================
LIST OF MODIFIED FILES
=============================================================================
src/emu/screen.cpp
src/emu/screen.h
src/emu/ui/ui.cpp
src/emu/video.cpp src/
osd/windows/input.cpp src/osd/ui/ui.cpp
src/emu/video.cpp src/osd/windows/input.cpp
src/osd/modules/render/d3d/d3d9intf.cpp
=============================================================
CRT-MAME 0.168 — V1.3
Change Notes
==================================================================
1) SLIDER REFRESH RATE CFG — Initial Save (V1)
Functionality:
Automatic saving and reloading of the user's frequency in:
cfg/[game_name].cfg
Modified files:
src/emu/video.cpp → config_save_screen_refresh() / config_load_screen_refresh()
-------------------------------------------------------------------------------------------------------------------------------
2) SLIDER REFRESH RATE — Improvements (V2)
Improvements:
Slider in the TAB menu is functional and stable.
Rounded to 3 decimal places for exact consistency.
Saved only if there is a real difference.
Added original frequency for proper comparison.
Immediate application of the new frequency.
Modified files:
src/emu/ui/ui.cpp — slider_refresh()
src/emu/screen.h — m_user_refresh_rate, setters/getters, default value
src/emu/screen.cpp — initialization default refresh
src/emu/video.cpp — V2 CFG load/save
NEW FUNCTION ADDED in src/emu/video.cpp:
//-------------------------------------------------
// HARDCADE config_load_screen_refresh - load screen refresh rates EDIT
//---------------------------------------
static void config_load_screen_refresh(running_machine &machine, int cfg_type, xml_data_node *parentnode)
{
// only load game configurations
if (cfg_type != CONFIG_TYPE_GAME || parentnode == NULL)
return;
// iterate over screen nodes
for (xml_data_node *screennode = xml_get_sibling(parentnode->child, "screen");
screennode != NULL;
screennode = xml_get_sibling(screennode->next, "screen"))
{
// get the screen tag
const char *screen_tag = xml_get_attribute_string(screennode, "tag", "");
if (screen_tag
== 0)
continues; continues;
// find the matching screen device
screen_device_iterator iter(machine.root_device());
for (screen_device *screen = iter.first(); screen != NULL; screen = iter.next ())
{
if (strcmp(screen->tag(), screen_tag) == 0)
{
// load and apply the refresh rate
double refresh = xml_get_attribute_float(screennode, "refresh", 0.0);
if (refresh > 0.0)
{
// store the user refresh rate
screen->set_user_refresh_rate(refresh);
// apply it immediately
int width = screen->width();
int height = screen->height();
const rectangle &visarea = screen->visible_area();
screen->configure(width, height, visaarea, HZ_TO_ATTOSECONDS(refresh));
}
break;
} }
}
}
//
------------------------------------------------
// HARDCADE config_save_screen_refresh - save screen refresh rates
//------------------------------------------------
static void config_save_screen_refresh(running_machine &machine, int cfg_type, xml_data_node *parentnode)
{
// only save game configurations
if (cfg_type != CONFIG_TYPE_GAME || parentnode == NULL)
return;
// iterate over all screens
screen_device_iterator iter(machine.root_device());
for (screen_device *screen = iter.first(); screen != NULL; screen = iter.next ())
{
// only save if user has set a custom refresh rate
double refresh = screen->user_refresh_rate();
if (refresh > 0.0)
{
// create a screen node
xml_data_node *screennode = xml_add_child(parentnode, "screen", NULL);
if (screennode != NULL)
{
xml_set_attribute(screennode, "tag", screen->tag());
xml_set_attribute_float(screennode, "refresh", refresh);
}
}
}
}
FUNCTION MODIFIED IN in src/emu/ui/ui.cpp:
//-------------------------------------------------
// HARDCADE slider_refresh - refresh rate slider callback
//-------------------------------------------------
static INT32 slider_refresh(running_machine &machine, void *arg, std::string *str, INT32 newval)
{
screen_device *screen = reinterpret_cast<screen_device *>(arg);
double defrefresh = screen->default_refresh_rate();
double refresh;
if (newval != SLIDER_NOCHANGE)
{
int width = screen->width();
int height = screen->height();
const rectangle &visarea = screen->visible_area();
// new raw value
double new_refresh = defrefresh + (double)newval * 0.001;
// APPLY the value (imprecise, that's normal)
screen->configure(width, height, visarea, HZ_TO_ATTOSECONDS(new_refresh));
// ROUNDED for display and saving
double rounded_new = floor(new_refresh * 1000.0 + 0.5) / 1000.0;
double rounded_default = floor(defrefresh * 1000.0 + 0.5) / 1000.0;
// SAVE only the rounded value
if (rounded_new != rounded_default)
screen->set_user_refresh_rate(rounded_new); // <-- CORRECT !
else
screen->set_user_refresh_rate(0.0);
}
if (str != NULL)
strprintf(*str, "%.3ffps", ATTOSECONDS_TO_HZ(screen->frame_period().attoseconds()));
refresh = ATTOSECONDS_TO_HZ(screen->frame_period().attoseconds());
return floor((refresh - defrefresh) * 1000.0 + 0.5);
}
-------------------------------------------------------------------------------------------------------------------------------
3) INPUT LAG — Windows Timestamp (V3)
Objective: To capture the exact moment an input is updated.
Functionality:
Added a high-precision timestamp during updates:
Win32 keyboard,
RawInput,
DirectInput (older USB + HID joysticks).
Modified files:
src/osd/windows/input.cpp
, added UINT64 last_timestamp to class device_info,
added devinfo->last_timestamp = osd_ticks(); to:
win32_keyboard_poll(),
rawinput_keyboard_update()
, dinput_joystick_poll().
-------------------------------------------------------------------------------------------------------------------------------
4) INPUT LAG — Late Input Polling (V3b)
Major new feature: Added Late Input Polling (LIP), reducing input lag by 1 frame.
- Device polling moved to just before input processing
- Reduces input lag by a full frame
- Added function: late_input_poll()
- Integrated into windows_osd_interface::update()
Principle:
Force ultra-late device polling just before Windows processes the frame's messages → fresher input = 1 frame saved.
Additions:
a) New function
src/osd/windows/input.cpp:
void late_input_poll(running_machine &machine)
{
osd_lock_acquire(input_lock);
if (keyboard_list) device_list_poll_devices(keyboard_list);
if (mouse_list) device_list_poll_devices(mouse_list);
if (lightgun_list) device_list_poll_devices(lightgun_list);
if (joystick_list) device_list_poll_devices(joystick_list);
last_poll = GetTickCount();
osd_lock_release(input_lock);
}
b) Call in the Windows loop (critical)
In src/osd/windows/video.cpp, function:
Insertion of late_input_poll(machine()) into windows_osd_interface::update()
Addition of extern void late_input_poll(running_machine &machine);
-------------------------------------------------------------------------------------------------------------------------------
5) Direct3D Optimization – "Direct Present Minimal"
A GPU optimization inspired by GroovyMAME allowing a reduction in display lag.
- Added features:
Immediate image presentation via SwapChain->Present()
Bypass of Direct3D internal buffering
Reduced GPU lag by approximately 1 frame
Compatible with Windows XP / Direct3D 9
Safe fallback if the option is not supported by the driver
- Modified files:
src/osd/modules/render/d3d/d3d9intf.cpp
- Complete replacement of the function:
device_present()
// HARDCADE MODIFICATION Direct Present Minimal //////////////////////////////////////
static HRESULT device_present(device *dev, const RECT *source, const RECT *dest, HWND override, RGNDATA *dirty, DWORD flags)
{
IDirect3DDevice9 *device = (IDirect3DDevice9 *)dev;
// If flags are provided, prefer presenting via the swapchain with those flags
if (flags != 0)
{
IDirect3DSwapChain9 *chain = NULL;
HRESULT result = IDirect3DDevice9_GetSwapChain(device, 0, &chain);
if (result == D3D_OK && chain != NULL)
{
result = IDirect3DSwapChain9_Present(chain, source, dest, override, dirty, flags);
IDirect3DSwapChain9_Release(chain);
return result;
}
}
// Attempt to present via the swapchain with FORCEIMMEDIATE if available.
// This is preferred because IDirect3DDevice9::Present() has no flags parameter.
IDirect3DSwapChain9 *chain = NULL;
HRESULT r = IDirect3DDevice9_GetSwapChain(device, 0, &chain);
if (r == D3D_OK && chain != NULL)
{
#ifdef D3DPRESENT_FORCEIMMEDIATE
HRESULT pres = IDirect3DSwapChain9_Present(chain, source, dest, override, dirty, D3DPRESENT_FORCEIMMEDIATE);
#else
// Fall back to zero flags if FORCEIMMEDIATE is not defined
HRESULT pres = IDirect3DSwapChain9_Present(chain, source, dest, override, dirty, 0);
#endif
IDirect3DSwapChain9_Release(chain);
return pres;
}
// Last-resort fallback: call device Present (no flags)
return IDirect3DDevice9_Present(device, source, dest, override, dirty);
}
// HARDCADE Direct Present Minimal ////////////////////////////////////
- Quick Description
The patch forces Direct3D to present the image as soon as possible, without going through the usual GPU queue.
Result: more direct display, image closer to real-time timing, better responsiveness at 15 kHz and LCD.
- Interaction with other optimizations
This optimization is in addition to:
Custom refresh rate saving
Late Input Polling
DirectInput / RawInput system improvements
No known incompatibilities.
Recommended INI configuration to benefit from the GPU improvement:
-video d3d
-waitvsync 0
-------------------------------------------------------------------------------------------------------------------------------
6) HARDCADE CRT-MAME — Adaptive HardSync
Adds a lightweight CPU synchronization system before video rendering, compatible with Windows XP and lower-end systems.
Effect:
Reduces micro-stuttering
Avoids premature Present() calls
No CPU load (Sleep(0))
Works with: DirectDraw, Direct3D, GDI
- Changes applied
Modified file: src/osd/windows/video.cpp
In the function:
void windows_osd_interface::update(bool skip_redraw)
A new block has been added before the Late Input Poll:
// HARDCADE CRT-MAME: Adaptive HardSync (reduced latency)
if (video_config.waitvsync == FALSE && video_config.syncrefresh == FALSE)
{
DWORD now = timeGetTime();
DWORD delta = now - last_event_check;
if (delta < 10)
{
Sleep(0); // yield CPU → stabilize the timing
}
}
- Objective of Adaptive HardSync:
Stabilize frame timing and prevent oscillations
; Reduce overall latency without blocking the machine
; Works even with old or slow video cards;
Simplified and safer version than true Hard Sync; GroovyMAME
=================================
VERSION
===================================
CRT-MAME 0.168 — V1.3
December 2025
© 2025 Hardcade – PlayRetro – Olivier Mileo
==================================================================================