Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dosbox-staging/dosbox-staging.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFeralChild64 <>2022-08-04 17:57:59 +0300
committerFeralChild64 <>2022-08-04 17:57:59 +0300
commit578c40ce81176865d36c3d31582877fa15a972d0 (patch)
tree770c6d7b98aaad85776ed7f59c748da3ec10f042
parent19fcfc5e045c531b4d036ee1c2207f1ca8d7a6a1 (diff)
Partially fix Ultima Underworld mouse issuesfc/ultima-underworld-mouse-fix-1
-rw-r--r--src/ints/mouse.cpp86
-rw-r--r--src/ints/mouse_core.h12
-rw-r--r--src/ints/mouse_dos_driver.cpp198
3 files changed, 205 insertions, 91 deletions
diff --git a/src/ints/mouse.cpp b/src/ints/mouse.cpp
index 23a762b72..6b299c53f 100644
--- a/src/ints/mouse.cpp
+++ b/src/ints/mouse.cpp
@@ -49,9 +49,20 @@ static bool raw_input = true; // true = relative input is raw data, without
static Bitu int74_ret_callback = 0;
// Original DOSBox forces mouse event delay of 5 milliseconds all the time, but
-// this is probably slightly too much - 7 milliseconds is already about twice the
-// typical 70 Hz screen refresh rate.
-static constexpr uint8_t boost_delay_ms = 7;
+// this is probably slightly too much - 8 milliseconds is already over twice the
+// 60 Hz rate the original Microsoft Mouse 8.20 driver sets
+// At least Ultima Underworld I and II do not like too high sampling rates
+static constexpr uint8_t boost_delay_ms = 8;
+
+static constexpr uint8_t mask_mouse_has_moved = static_cast<uint8_t>(MouseEventId::MouseHasMoved);
+static constexpr uint8_t mask_wheel_has_moved = static_cast<uint8_t>(MouseEventId::WheelHasMoved);
+static constexpr uint8_t mask_pressed = static_cast<uint8_t>(MouseEventId::PressedLeft) +
+ static_cast<uint8_t>(MouseEventId::PressedRight) +
+ static_cast<uint8_t>(MouseEventId::PressedMiddle);
+static constexpr uint8_t mask_released = static_cast<uint8_t>(MouseEventId::ReleasedLeft) +
+ static_cast<uint8_t>(MouseEventId::ReleasedRight) +
+ static_cast<uint8_t>(MouseEventId::ReleasedMiddle);
+static constexpr uint8_t mask_button = mask_pressed + mask_released;
// ***************************************************************************
// Debug code, normally not enabled
@@ -172,20 +183,8 @@ private:
bool event_dos_moved = false;
bool event_dos_wheel = false;
- bool prefer_ps2_next_time = false;
-
uint32_t pic_ticks_start = 0; // PIC_Ticks value when timer starts
- // Helper masks for aggregating DOS button events
- static constexpr uint8_t aggregate_mask_pressed =
- static_cast<uint8_t>(MouseEventId::PressedLeft) |
- static_cast<uint8_t>(MouseEventId::PressedRight) |
- static_cast<uint8_t>(MouseEventId::PressedMiddle);
- static constexpr uint8_t aggregate_mask_released =
- static_cast<uint8_t>(MouseEventId::ReleasedLeft) |
- static_cast<uint8_t>(MouseEventId::ReleasedRight) |
- static_cast<uint8_t>(MouseEventId::ReleasedMiddle);
-
// Helpers to check if there are events in the queue
bool HasEventDosMoved() const;
bool HasEventDosButton() const;
@@ -322,11 +321,11 @@ void MouseQueue::AggregateEventsDOS(MouseEvent &event)
// Generate masks to detect whether two button events can be
// aggregated (might be needed later even if we have no more
// events now)
- if (event.mask & aggregate_mask_pressed)
+ if (event.mask & mask_pressed)
// Set 'pressed+released' for every 'pressed' bit
event.aggregate_mask = static_cast<uint8_t>(event.mask |
(event.mask << 1));
- else if (event.mask & aggregate_mask_released)
+ else if (event.mask & mask_released)
// Set 'pressed+released' for every 'released' bit
event.aggregate_mask = static_cast<uint8_t>(event.mask |
(event.mask >> 1));
@@ -392,14 +391,8 @@ void MouseQueue::FetchEvent(MouseEvent &event)
return;
}
- // We should prefer PS/2 events now (as the last was DOS one),
- // but we can't if there is no PS/2 event ready to be handled
- if (!HasReadyEventPS2())
- prefer_ps2_next_time = false;
-
// Try DOS button events
- if (HasReadyEventDosButton() && !prefer_ps2_next_time) {
- prefer_ps2_next_time = true;
+ if (HasReadyEventDosButton()) {
// Set delay before next DOS events
delay.dos_button_ms = start_delay.dos_button_ms;
delay.dos_moved_wheel_ms = std::max(delay.dos_moved_wheel_ms,
@@ -412,7 +405,6 @@ void MouseQueue::FetchEvent(MouseEvent &event)
// Now try PS/2 event
if (HasReadyEventPS2()) {
- prefer_ps2_next_time = false;
// Set delay before next PS/2 events
delay.ps2_ms = start_delay.ps2_ms;
// PS/2 events are really dummy - merely a notification
@@ -640,8 +632,18 @@ static Bitu int74_handler()
event.request_ps2 ? "PS2" : "---",
event.request_dos ? "DOS" : "---");
- // If DOS driver is active, use it to handle the event
- if (event.request_dos && mouse_shared.active_dos) {
+ // Handle DOS events
+ if (event.request_dos) {
+ if ((event.mask & mask_mouse_has_moved) &&
+ !MOUSEDOS_UpdateMoved())
+ event.mask &= static_cast<uint8_t>(~mask_mouse_has_moved & 0xff);
+ if ((event.mask & mask_wheel_has_moved) &&
+ !MOUSEDOS_UpdateWheel())
+ event.mask &= static_cast<uint8_t>(~mask_wheel_has_moved & 0xff);
+ if ((event.mask & mask_button) &&
+ !MOUSEDOS_UpdateButtons(event.buttons_12S))
+ event.mask &= static_cast<uint8_t>(~mask_button & 0xff);
+
// Taken from DOSBox X: HERE within the IRQ 12 handler is the
// appropriate place to redraw the cursor. OSes like Windows 3.1
// expect real-mode code to do it in response to IRQ 12, not
@@ -743,27 +745,36 @@ void MOUSE_NewScreenParams(const uint16_t clip_x, const uint16_t clip_y,
MOUSE_NotifyStateChanged();
}
+uint8_t clamp_start_delay(float value_ms)
+{
+ constexpr long min_ms = 3; // 330 Hz sampling rate
+ constexpr long max_ms = 100; // 10 Hz sampling rate
+
+ const auto tmp = std::clamp(std::lround(value_ms), min_ms, max_ms);
+ return static_cast<uint8_t>(tmp);
+}
+
void MOUSE_NotifyRateDOS(const uint8_t rate_hz)
{
auto &val_moved_wheel = queue.start_delay.dos_moved_wheel_ms;
auto &val_button = queue.start_delay.dos_button_ms;
// Convert rate in Hz to delay in milliseconds
- val_moved_wheel = static_cast<uint8_t>(1000 / rate_hz);
+ val_moved_wheel = clamp_start_delay(1000.0f / rate_hz);
+
// Cheat a little, do not allow to set too high delay. Some old
// games might have tried to set lower rate to reduce number of IRQs
// and save CPU power - this is no longer necessary. Windows 3.1 also
// sets a suboptimal value in its PS/2 driver.
val_moved_wheel = std::min(val_moved_wheel, boost_delay_ms);
+
// Cheat a little once again - our delay for buttons is separate and
// much smaller, so that button events can be sent to the DOS
- // application with minimal latency. Due to hardware differences and
- // multiple independent mouse driver implementations in the past, it is
- // unlikely to cause problems. Besides, host OS and hardware have their
- // limitations, too, they also introduce some latency.
- val_button = val_moved_wheel / 5;
+ // application with minimal latency. So far this didin't cause any issues.
+ val_button = clamp_start_delay(val_moved_wheel / 5.0f);
- // TODO: make a configuration option(s) for this, same for PS/2 mouse
+ // TODO: make a configuration option(s) for boosting sampling rates,
+ // for DOS and PS/2 mice alike
}
void MOUSE_NotifyRatePS2(const uint8_t rate_hz)
@@ -771,7 +782,8 @@ void MOUSE_NotifyRatePS2(const uint8_t rate_hz)
auto &val_ps2 = queue.start_delay.ps2_ms;
// Convert rate in Hz to delay in milliseconds
- val_ps2 = static_cast<uint8_t>(1000 / rate_hz);
+ val_ps2 = clamp_start_delay(1000.0f / rate_hz);
+
// Cheat a little, like with DOS driver
val_ps2 = std::min(val_ps2, boost_delay_ms);
}
@@ -894,7 +906,7 @@ void MOUSE_EventPressed(uint8_t idx)
}
if (changed_12S) {
event.request_vmm = MOUSEVMM_NotifyPressedReleased(buttons_12S);
- event.request_dos = MOUSEDOS_NotifyPressed(buttons_12S, idx_12S);
+ event.request_dos = true;
}
queue.AddEvent(event);
@@ -948,7 +960,7 @@ void MOUSE_EventReleased(uint8_t idx)
get_buttons_joined());
if (changed_12S) {
event.request_vmm = MOUSEVMM_NotifyPressedReleased(buttons_12S);
- event.request_dos = MOUSEDOS_NotifyReleased(buttons_12S, idx_12S);
+ event.request_dos = true;
MOUSESERIAL_NotifyPressedReleased(buttons_12S, idx_12S);
}
diff --git a/src/ints/mouse_core.h b/src/ints/mouse_core.h
index 08afa0433..3ce1a5c2c 100644
--- a/src/ints/mouse_core.h
+++ b/src/ints/mouse_core.h
@@ -248,10 +248,14 @@ uintptr_t MOUSEDOS_DoCallback(const uint8_t mask, const MouseButtons12S buttons_
// - understands up to 3 buttons
// - needs index of button which changed state
-bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel,
- const uint16_t x_abs, const uint16_t y_abs);
-bool MOUSEDOS_NotifyPressed(const MouseButtons12S buttons_12S, const uint8_t idx);
-bool MOUSEDOS_NotifyReleased(const MouseButtons12S buttons_12S, const uint8_t idx);
+bool MOUSEDOS_NotifyMoved(const float x_rel,
+ const float y_rel,
+ const uint16_t x_abs,
+ const uint16_t y_abs);
bool MOUSEDOS_NotifyWheel(const int16_t w_rel);
+bool MOUSEDOS_UpdateMoved();
+bool MOUSEDOS_UpdateButtons(const MouseButtons12S buttons_12S);
+bool MOUSEDOS_UpdateWheel();
+
#endif // DOSBOX_MOUSE_CORE_H
diff --git a/src/ints/mouse_dos_driver.cpp b/src/ints/mouse_dos_driver.cpp
index bc144cd66..5b95c6897 100644
--- a/src/ints/mouse_dos_driver.cpp
+++ b/src/ints/mouse_dos_driver.cpp
@@ -58,9 +58,29 @@ enum class MouseCursor : uint8_t { Software = 0, Hardware = 1, Text = 2 };
static MouseButtons12S buttons = 0;
static float pos_x = 0.0f;
static float pos_y = 0.0f;
-static int16_t wheel_counter = 0;
+static int8_t counter_w = 0;
static uint8_t rate_hz = 0; // TODO: add proper reaction for 0 (disable driver)
+static struct {
+ // Data from mouse events which were already received,
+ // but not necessary visible to the application
+
+ // Mouse movement
+ float x_rel = 0.0f;
+ float y_rel = 0.0f;
+ uint16_t x_abs = 0;
+ uint16_t y_abs = 0;
+
+ // Wheel movement
+ int16_t w_rel = 0;
+
+ void Reset() {
+ x_rel = 0.0f;
+ y_rel = 0.0f;
+ w_rel = 0;
+ }
+} pending;
+
static struct { // DOS driver state
// Structure containing (only!) data which should be
@@ -157,6 +177,15 @@ static RealPt user_callback;
// Common helper routines
// ***************************************************************************
+static uint8_t signed_to_reg8(const int8_t x)
+{
+ if (x >= 0)
+ return static_cast<uint8_t>(x);
+ else
+ // -1 for 0xff, -2 for 0xfe, etc.
+ return static_cast<uint8_t>(0x100 + x);
+}
+
static uint16_t signed_to_reg16(const int16_t x)
{
if (x >= 0)
@@ -180,6 +209,15 @@ static int16_t reg_to_signed16(const uint16_t x)
return static_cast<int16_t>(x);
}
+int8_t clamp_to_int8(const int32_t val)
+{
+ const auto tmp = std::clamp(val,
+ static_cast<int32_t>(INT8_MIN),
+ static_cast<int32_t>(INT8_MAX));
+
+ return static_cast<int8_t>(tmp);
+}
+
static uint16_t get_pos_x()
{
return static_cast<uint16_t>(std::lround(pos_x)) & state.granularity_x;
@@ -526,14 +564,11 @@ static uint8_t get_reset_wheel_8bit()
if (!state.cute_mouse) // wheel requires CuteMouse extensions
return 0;
- const int16_t tmp = static_cast<int16_t>(
- std::clamp(wheel_counter,
- static_cast<int16_t>(INT8_MIN),
- static_cast<int16_t>(INT8_MAX)));
- wheel_counter = 0; // reading always clears the counter
+ const auto tmp = counter_w;
+ counter_w = 0; // reading always clears the counter
// 0xff for -1, 0xfe for -2, etc.
- return static_cast<uint8_t>((tmp >= 0) ? tmp : (0x100 + tmp));
+ return signed_to_reg8(tmp);
}
static uint16_t get_reset_wheel_16bit()
@@ -541,11 +576,10 @@ static uint16_t get_reset_wheel_16bit()
if (!state.cute_mouse) // wheel requires CuteMouse extensions
return 0;
- const int16_t tmp = wheel_counter;
- wheel_counter = 0; // reading always clears the counter
+ const int16_t tmp = counter_w;
+ counter_w = 0; // reading always clears the counter
- // 0xffff for -1, 0xfffe for -2, etc.
- return static_cast<uint16_t>((tmp >= 0) ? tmp : (0x10000 + tmp));
+ return signed_to_reg16(tmp);
}
static void set_mickey_pixel_rate(const int16_t ratio_x, const int16_t ratio_y)
@@ -611,9 +645,10 @@ static void set_interrupt_rate(const uint16_t rate_id)
static void reset_hardware()
{
- wheel_counter = 0;
+ counter_w = 0;
set_interrupt_rate(4);
PIC_SetIRQMask(12, false); // lower IRQ line
+ MOUSE_NotifyRateDOS(60); // same rate Microsoft Mouse 8.20 sets
}
void MOUSEDOS_BeforeNewVideoMode()
@@ -695,6 +730,11 @@ void MOUSEDOS_AfterNewVideoMode(const bool setmode)
static void reset()
{
+ // Although these do not belong to the driver state,
+ // reset them too to avoid any possible problems
+ counter_w = 0;
+ pending.Reset();
+
MOUSEDOS_BeforeNewVideoMode();
MOUSEDOS_AfterNewVideoMode(false);
@@ -835,8 +875,7 @@ static void move_cursor_seamless(const float x_rel, const float y_rel,
}
}
-bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel,
- const uint16_t x_abs, const uint16_t y_abs)
+bool MOUSEDOS_UpdateMoved()
{
const auto old_pos_x = get_pos_x();
const auto old_pos_y = get_pos_y();
@@ -844,13 +883,17 @@ bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel,
const auto old_mickey_x = static_cast<int16_t>(state.mickey_x);
const auto old_mickey_y = static_cast<int16_t>(state.mickey_y);
- const auto x_mov = MOUSE_ClampRelativeMovement(x_rel * sensitivity_dos);
- const auto y_mov = MOUSE_ClampRelativeMovement(y_rel * sensitivity_dos);
+ const auto x_mov = MOUSE_ClampRelativeMovement(pending.x_rel * sensitivity_dos);
+ const auto y_mov = MOUSE_ClampRelativeMovement(pending.y_rel * sensitivity_dos);
+
+ // Pending relative movement is now consummed
+ pending.x_rel = 0.0f;
+ pending.y_rel = 0.0f;
if (mouse_is_captured)
move_cursor_captured(x_mov, y_mov);
else
- move_cursor_seamless(x_mov, y_mov, x_abs, y_abs);
+ move_cursor_seamless(x_mov, y_mov, pending.x_abs, pending.y_abs);
// Make sure cursor stays in the range defined by application
limit_coordinates();
@@ -863,8 +906,6 @@ bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel,
static_cast<int16_t>(state.mickey_x)) ||
(old_mickey_y !=
static_cast<int16_t>(state.mickey_y));
- if (!abs_changed && !rel_changed)
- return false;
// NOTE: It might be tempting to optimize the flow here, by skipping
// the whole event-queue-callback flow if there is no callback
@@ -874,53 +915,110 @@ bool MOUSEDOS_NotifyMoved(const float x_rel, const float y_rel,
// constantly (don't ask me, why) - doing too much optimization
// can cause the game to skip mouse events.
- return true;
+ return abs_changed || rel_changed;
}
-bool MOUSEDOS_NotifyPressed(const MouseButtons12S new_buttons_12S, const uint8_t idx)
+bool MOUSEDOS_UpdateButtons(const MouseButtons12S new_buttons_12S)
{
- assert(idx < num_buttons);
+ if (buttons.data == new_buttons_12S.data)
+ return false;
- buttons = new_buttons_12S;
+ auto mark_pressed = [](const uint8_t idx) {
+ state.last_pressed_x[idx] = get_pos_x();
+ state.last_pressed_y[idx] = get_pos_y();
+ };
+
+ auto mark_released = [](const uint8_t idx) {
+ state.last_released_x[idx] = get_pos_x();
+ state.last_released_y[idx] = get_pos_y();
+ };
+
+ if (new_buttons_12S.left && !buttons.left)
+ mark_pressed(0);
+ else if (!new_buttons_12S.left && buttons.left)
+ mark_released(0);
+
+ if (new_buttons_12S.right && !buttons.right)
+ mark_pressed(1);
+ else if (!new_buttons_12S.right && buttons.right)
+ mark_released(1);
- ++state.times_pressed[idx];
- state.last_pressed_x[idx] = get_pos_x();
- state.last_pressed_y[idx] = get_pos_y();
+ if (new_buttons_12S.middle && !buttons.middle)
+ mark_pressed(2);
+ else if (!new_buttons_12S.middle && buttons.middle)
+ mark_released(2);
- // See comment in 'MOUSEDOS_NotifyMoved' about optimizing this
+ buttons = new_buttons_12S;
return true;
}
-bool MOUSEDOS_NotifyReleased(const MouseButtons12S new_buttons_12S, const uint8_t idx)
+bool MOUSEDOS_UpdateWheel()
{
- assert(idx < num_buttons);
+ counter_w = clamp_to_int8(static_cast<int32_t>(counter_w + pending.w_rel));
- buttons = new_buttons_12S;
+ // Pending wheel scroll is now consummed
+ pending.w_rel = 0;
- ++state.times_released[idx];
- state.last_released_x[idx] = get_pos_x();
- state.last_released_y[idx] = get_pos_y();
+ state.last_wheel_moved_x = get_pos_x();
+ state.last_wheel_moved_y = get_pos_y();
- // See comment in 'MOUSEDOS_NotifyMoved' about optimizing this
- return true;
+ return (counter_w != 0);
+}
+
+bool MOUSEDOS_NotifyMoved(const float x_rel,
+ const float y_rel,
+ const uint16_t x_abs,
+ const uint16_t y_abs)
+{
+ // Check if an event is needed
+ bool event_needed = false;
+ if (mouse_is_captured) {
+ // Uses relative mouse movements - processing is too complicated
+ // to easily predict whether the event can be safely omitted
+ event_needed = true;
+ // TODO: it actually can be done - but it will require some refactoring
+ } else {
+ // Uses absolute mouse position (seamless mode), relative movements
+ // can wait to be reported - they are completely unreliable anyway
+ if (pending.x_abs != x_abs || pending.y_abs != y_abs)
+ event_needed = true;
+ }
+
+ // Update values to be consummed when the event arrives
+ pending.x_rel = MOUSE_ClampRelativeMovement(pending.x_rel + x_rel);
+ pending.y_rel = MOUSE_ClampRelativeMovement(pending.y_rel + y_rel);
+ pending.x_abs = x_abs;
+ pending.y_abs = y_abs;
+
+ // NOTES:
+ //
+ // It might be tempting to optimize the flow here, by skipping
+ // the whole event-queue-callback flow if there is no callback
+ // registered, no graphic cursor to draw, etc. Don't do this - there
+ // is at least one game (Master of Orion II), which performs INT 0x33
+ // calls with 0x0f parameter (changing the callback settings)
+ // constantly (don't ask me, why) - doing too much optimization
+ // can cause the game to skip mouse events.
+ //
+ // Also, do not update mouse state here - Ultima Underworld I and II
+ // do not like mouse states updated in real time, it ends up with
+ // mouse movements being ignored by the game randomly.
+
+ return event_needed;
}
bool MOUSEDOS_NotifyWheel(const int16_t w_rel)
{
- if (!state.cute_mouse) // wheel only available if CuteMouse extensions
- // are active
+ if (!state.cute_mouse)
return false;
- const auto tmp = std::clamp(static_cast<int32_t>(w_rel + wheel_counter),
- static_cast<int32_t>(INT16_MIN),
- static_cast<int32_t>(INT16_MAX));
+ // Although in some places it is possible for the guest code to get
+ // wheel counter in 16-bit format, scrolling hundreds of lines in one
+ // go would be insane - thus, limit the wheel counter to 8 bits and
+ // reuse the code written for other mouse modules
+ pending.w_rel = clamp_to_int8(pending.w_rel + w_rel);
- wheel_counter = static_cast<int16_t>(tmp);
- state.last_wheel_moved_x = get_pos_x();
- state.last_wheel_moved_y = get_pos_y();
-
- // See comment in 'MOUSEDOS_NotifyMoved' about optimizing this
- return true;
+ return (pending.w_rel != 0);
}
static Bitu int33_handler()
@@ -1117,11 +1215,11 @@ static Bitu int33_handler()
MOUSEDOS_DrawCursor();
break;
case 0x11: // CuteMouse - get mouse capabilities
- reg_ax = 0x574d; // Identifier for detection purposes
- reg_bx = 0; // Reserved capabilities flags
- reg_cx = 1; // Wheel is supported
+ reg_ax = 0x574d; // Identifier for detection purposes
+ reg_bx = 0; // Reserved capabilities flags
+ reg_cx = 1; // Wheel is supported
+ counter_w = 0;
state.cute_mouse = true; // This call enables CuteMouse extensions
- wheel_counter = 0;
// Previous implementation provided Genius Mouse 9.06 function
// to get number of buttons
// (https://sourceforge.net/p/dosbox/patches/32/), it was