libretro / pcsx_rearmed

ARM optimized PCSX fork
GNU General Public License v2.0
165 stars 116 forks source link

[WIP] Basic Konami lightgun (Justifier / Hyper Blaster) support #723

Closed StormedBubbles closed 9 months ago

StormedBubbles commented 1 year ago

Hi,

Konami's Justifier / Hyper Blaster is currently unsupported, which makes it impossible to play Area 51 (and any other non-GunCon lightgun game lacking a GunCon patch) using a lightgun device.

I filled in the prerequisite menu items in my fork of this emulator, did a bit of research, and was able to get the "konami gun" to be recognized by compatible games. However, I am having issues getting the coordinate tracking to work and am reaching out hoping to find someone who can help.

Here is what is currently working:

Now, the issue is that I don't know how to get the emulator to recognize the coordinates and place shots on the screen. Currently, I can indeed fire shots, but none of them lands on screen. They are all considered "reload" shots. The GunCon support added by Mr. Sinden a few years ago takes the coordinate data from the backend plugin code...

stdpar[4] = 0x5a - (xres - 256) / 3 + (((xres - 256) / 3 + 356) * absX >> 10);
stdpar[5] = (0x5a - (xres - 256) / 3 + (((xres - 256) / 3 + 356) * absX >> 10)) >> 8;
stdpar[6] = 0x20 + (yres * absY >> 10);
stdpar[7] = (0x20 + (yres * absY >> 10)) >> 8;

...and then plugs in the RetroArch gun coordinates after that. Plugging in the RetroArch coordinates and figuring out any scaling and shifting needed after that is easy. The hard part (for me, at least) is understanding how the Konami gun's scanline/timing data can be translated to an (x,y) position and how that data would get sent to the control bus in a similar fashion to how the GunCon data are sent in the code snippet above.

I really have no clue where those coordinate formulas came from, and I am hoping that someone knows what needs to be changed from the GunCon bits to get the Konami gun to recognize the coordinates.

Thanks to @Widge-5 for testing. Anyone who wants to check it out can compile the justifier branch of my fork. You will be able to press all of the above buttons but won't be able to hit anything. There isn't much use in that, so that's why this was posted as an issue instead of a pull request.

Thanks for reading. I hope that someone is able to help 😃

Widge-5 commented 1 year ago

I'll just add some information about the testing I did on @StormedBubbles' fork with the partial Konami Gun implementation, and comparison against real hardware.

I found that most HyperBlaster/Justifier compatible games would recognise the Konami Gun device type as a valid device. For example Area 51 shows an image of a gun at the bottom of the menu screens for each player with this device type used, or a controller if a controller device type is used, and in-game trigger pulls would cause an on-screen flash as expected. That is consistent with real hardware. And games that use the buttons recognised the Gun Start input as analogous to the HyperBlaster's Start button on its left side, and the Gun Aux A input as the Hyperbalster's back button.

The only game I tested that didn't recognise the input type was Die Hard Trilogy 2, which advertises compatibility with both Guncon and Konami guns. DHT2 showed a message on screen that the device connected in Port X was incompatible. To compare this with real hardware I will first say that I don't have an authentic Konami HyperBlaster/Justifier, I have 2 third party equivalent guns : a Logic3 Predator which has a connector for both Sega Saturn and PS1, which purports to be a Konami-compatible gun, and a Cocoto Funfair gun which has a 3-way switch with Normal, Guncon1 and Guncon2 modes; Normal mode is the Konami-compatible mode. When using the Logic3 Predator gun on a real Playstation with DHT2, I get the same error message that the device is not a compatible controller, but the Cocoto gun in Normal mode is accepted as a compatible device and can be used. I suspect that not all 3rd Party Konami-compatible guns are perfect clones and DHT2 can somehow tell the difference when none of my other games can. Logic3 failed to make a perfect clone, but somehow the makers of the Cocoto gun (Neko?) succeeded. I also suspect that perhaps the device emulation is of an imperect clone rather than the real thing. Fortunately, as the only game apparently affected by this is DHT2, this isn't a big problem as DHT2 also accepts Guncon input.

So overlooking that one issue, the matter still remains that the Konami Gun device type cannot "see" the screen. Hopefuly someone here will be able to figure out what the next step is to get the coordinates tracking to work.

StormedBubbles commented 1 year ago

Thanks. This PDF documenting most of the ins and outs of the various PlayStation input devices was sent to me recently. Of particular interest to this issue is the content on pages 164 and 165.

The document notes this about the Konami gun:

The coordinates aren't part of the controller data, instead they must be read from Timer 0 and 1 upon receiving IRQ10

I take this to mean that there is a fundamental incompatibility with my attempts to feed coordinates to the emulator in a similar fashion to the way the GunCon coordinates are. The GunCon coordinates are fed in the same way that the controller analog axes are.

The document notes that the Konami gun relies on timing data. The document does indeed even include coordinate formulas:

IF NTSC then X=(Timer0-140)*0.198166, Y=Timer1 
IF PAL then X=(Timer0-140)*0.196358, Y=Timer1

This is awesome and gives a good start on converting the -32767 to 32767 RetroArch lightgun range of both axes to the appropriate X and Y values. I do not have a good understanding of the units of measurement of the timers, but if the range of values for Timer0 is 0 to 320, then I think the converted coordinate for X would be something like this for player 1 in NTSC mode:

int gunx = input_state_cb(0, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
Timer0 = (gunx + 32767) * 320 / 65534;
X=(Timer0-140)*0.198166;

I could be mixing up my units of measurement with the timer, but that is the gist. However, with all that being said, I don't know where this information would be sent in order for the emulator to read the coordinates.

StormedBubbles commented 11 months ago

Hi again. What I wrote above about the coordinate scaling is a little inaccurate, but I believe I have that as well as offscreen shots sorted out. However, I don't quite understand what needs to happen with those scaled coordinates. I figure that they have to be sent through the controller port just like all other devices, but the IRQ10 stuff is throwing me off. I do see a lot of mentions of "IRQ" when doing a search through the repo, but I don't understand how it relates to the Konami gun. It seems that a check has to happen immediately upon pressing the trigger input in order to place the coordinate correctly onscreen. Any information anyone could provide would be greatly appreciated.

StormedBubbles commented 11 months ago

@notaz sorry to bother you. I am curious if you have insight on this.

I got the Justifier / Hyper Blaster recognized on my build. I would like to contribute this upstream, but one aspect is giving me issues.

The trigger, start, and back/dot button inputs all work, and I have an understanding on how to scale Libretro's lightgun coordinates to the timer values thanks to pp. 164–165 of this document, but I do not have an understanding of where those timer values should go.

Other lightgun devices I have explored send the coordinate data through the controller port, but it appears not to be so with Konami's PlayStation lightgun.

As stated above, I am just curious if there is any insight you could provide to help get coordinate tracking to work.

The addition of this device would make Area 51 and the original Mighty Hits playable with lightgun devices (they do not have GunCon patches) and would possibly improve the experience with a few of the Justifier games that currently rely on slightly buggy GunCon patches.

notaz commented 11 months ago

This needs IRQ10 to be arranged in such a way that it triggers at specific time (of emulation) that when the game reads root counters 0 and 1 (when it processes that same IRQ10 interrupt) it will get a desired screen position. However the problem is that the needed root counter modes are not currently emulated at all, so that needs to be implemented first. Also there is a problem of how this emu triggers interrupts, it only does it at the branches so this will introduce further jitter. All this could probably be worked around with hacks that would just feed the game the values we want when it reads the root counters, but those would be nasty and likely break other games.

That said if you can publish your branch with everything working except the game getting the right coordinates maybe I can take a look sometime, but no promises.

StormedBubbles commented 11 months ago

Thanks so much. The branch is located here. The emulator already had a placeholder PSE_PAD_TYPE_GUN for the Justifier as well as the label konami gun for the RetroArch menu, so I kept those. Any functions I added refer to the device as JUSTIFIER like how the GUNCON functions use the North American naming of the device. I synced with upstream (Libretro) before your commits from a few hours ago.

notaz commented 10 months ago

I've just added some support, at least Crypt Killer seems to work with that if you calibrate the gun in the options menu (but I left the last part out, see below).

There is now a new function used to pass the coords and it then calculates timing and emulates the interrupt. The function is called psxScheduleIrq10 and should probably be called from update_input_justifier(). I've left that out because I don't have a good formula to convert from libretro's coordinates to what the game expects. psxScheduleIrq10 arguments are:

void psxScheduleIrq10(int irq_count, int x_cycles, int y);

irq_count - according to nocash docs the gun detects the TV electron beam on several adjacent lines. This specifies on how many lines to generate the interrupt. Something like 4 should maybe be good. x_cycles - specifies horizontal coordinate in CPU cycles. Range is 0-2146, but note that in PAL/NTSC signal part of line is not visible (retrace/blanking) so range should be smaller. Should probably pass some_const_number + x * some_quotient. y - this one is simple, y coordinate in game's vertical resolution.

I've implemented the minimum timer modes, so probably some games will still not work. If somebody lists them here I can have a look.

Tovarichtch commented 10 months ago

Superb work!

Here's the entire list that uses the Hyper Blaster light gun:

  1. Area 51
  2. Maximum Force
  3. Crypt Killer
  4. Die Hard Trilogy (airport scene)
  5. Lethal Enforcer 1 & 2
  6. Policenauts (gun scenes)
  7. Project Horned Owl
  8. Star Wars: Rebel Assault II (shooting levels)
  9. Mighty Hits Special
  10. Elemental Gearbolt (JPN version only)
  11. Judge Dredd (EUR version only)
StormedBubbles commented 10 months ago

Thanks so much! I will tinker with the coordinates to see what the scaling should be.

In addition to the games listed above, the non-special version original version of Mighty Hits, Die Hard Trilogy 2, and the Moorhen/Moorhuhn games are supposed to recognize this peripheral.

Widge-5 commented 10 months ago
  1. Policenauts (gun scenes)

Small correction here. The Playstation release of Policenauts didn't natively support any lightgun, a GunCon patch was later made by the community.

Also, add to the list Silent Hill (PAL) which has an Easter Egg that makes use of the Hyper Blaster.

Finally, the Moorhuhn games have also been mentioned, all three of those games support both Guncon and Konami guns, but in a weird twist the Konami guns have more accurate aim than Guncon.

Tovarichtch commented 10 months ago
  1. Policenauts (gun scenes)

Small correction here. The Playstation release of Policenauts didn't natively support any lightgun, a GunCon patch was later made by the community.

You are absolutely right. My bad. I had the Saturn game in my mind while writing.

StormedBubbles commented 10 months ago

@notaz were you able to shoot all around the screen in Crypt Killer?

I tried a number of different things, but here is what always happened with Crypt Killer (NTSC) for me:

Without calibrating in the options menu: all shots landed either at the bottom-left corner or were offscreen

After calibrating in the options menu: all shots landed either dead center, just above dead center, or offscreen

The Libretro coordinates (I call them gunx and guny) are in the range [-32767,32767]. I used the PDF I quoted in the first post to create these formulas:

int gunx_scaled = ((gunx + 32767) / (65534 * .198166f) * vout_width) + 140;
int guny_scaled = (guny + 32767) / 65534 * vout_height;

Maybe not the most elegant way to write them, but this should give a range of [140,1752] for the horizontal coordinate if vout_width is 320 and a range of [0,vout_height] for the vertical coordinate, which at least sounds reasonable.

First of all, are those the right resolution variables? I tried just plugging in explicit numbers there but got the same result anyway.

I used irq_count = 4 and called psxScheduleIrq10(irq_count, gunx_scaled, guny_scaled) as the last thing inside of update_input_justifier(). That seemed most logical to me since some games (Area 51, Moorhuhn/Moorhen) require the use of the Justifier as a pointer in menus (i.e., the coordinate needs to be sent regardless of whether the trigger has just been pulled).

This is all in the justifier branch of my fork that I linked above.

EDIT: Link to the attempt I made at sending the Libretro coordinates

notaz commented 10 months ago
(guny + 32767) / 65534 * vout_height

This is doing integer math, left to right, so after the division you get only 2 possible outcomes (0 or 1), after final multiply it's 0 or vout_height. You should either multiply first and then divide, or force floating point with constants like 32767.0f.

psxScheduleIrq10(irq_count, gunx_scaled, guny_scaled);

Note that this should not be called at all when the gun points away from screen.

StormedBubbles commented 10 months ago

Thanks for that. I'm unfortunately getting similar results. The function psxScheduleIrq10() is definitely making a difference, though, because excluding it means that no shots ever land onscreen in Crypt Killer.

I pasted my update_input_justifier() modifications below. This is the entire function. Without calibration in Crypt Killer, all shots that land onscreen hit near the bottom-left corner. With calibration, all hit roughly the center of the screen. And that sometimes isn't consistent either. Even when trying to aim at the same spot, I will sometimes fire one shot, sometimes fire two shots, and sometimes reload (the game thinks I'm shooting offscreen).

I tried tinkering with the irq_count (8 seems to reduce the number of unintended offscreen shots). I also tried inserting constants for the second and third arguments of psxScheduleIrq10() and removing the distinction between onscreen and offscreen just to see if the shots would land somewhere else consistently, but that didn't work for me.

Should I watch out for any Libretro core options interfering with the usability of this new function?

static void update_input_justifier(int port, int ret)
{
   int irq_count = 4;

   int gunx = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X);
   int guny = input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y);

   int gunx_scaled = ((gunx + 32767.0f) * vout_width / (65534.0f * .198166f)) + 140.0f;
   int guny_scaled = (guny + 32767.0f) * vout_height / 65534.0f;

   if (!(input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN)) && !(input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD)))
   {
      psxScheduleIrq10(irq_count, gunx_scaled, guny_scaled);
   }

   // Trigger
   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_TRIGGER) || input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_RELOAD))
      in_keystate[port] |= (1 << DKEY_SQUARE);

   // Back
   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_AUX_A))
      in_keystate[port] |= (1 << DKEY_CROSS);

   // Start
   if (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_START))
      in_keystate[port] |= (1 << DKEY_START);

}
notaz commented 10 months ago

Hmm there seems to be a bug breaking the dynarec at least. Do you have it on? I did the development on the interpreter so missed it.

StormedBubbles commented 10 months ago

Yes, dynarec was an issue, and that issue appears to be resolved by your recent commits. Thanks!

It appears that different horizontal resolutions give different coefficients and that there is not a linear relationship between the X resolution and the coefficient. Below is the NTSC data set. I have the PAL set too and will try that out. The 320 coefficient gives seemingly perfect alignment in NTSC Crypt Killer after calibration, while NTSC Area 51 has a different resolution and seems to work well. I'm not familiar with how accurate these games were on real hardware other than knowing that the Lethal Enforcers collection has some accuracy issues on real hardware. NTSC Area 51 sprays the bullets a bit.

X Resolution Coefficient
256 0.158532
320 0.198166
384 0.226475
512 0.317065
640 0.396332

Reloading works. I only send the coordinates when the gun is pointed at the screen and when the RetroArch "simulated offscreen shot" button is not being pressed. Pointing away from the screen and shooting registers as an offscreen shot. I will have to get back to you on the reload button because I forgot to check that that works.

It will just take some tinkering to find the right combination of values (enhanced resolution may need to be considered separately), but it's getting there!

One thing that is definitely an issue is 2 players. I can use either gun by itself, but they interfere with each other if used simultaneously. The button presses are unique to each gun, but only one gun seems to be able to send coordinates at a time as things are currently. I think psxScheduleIrq10() may need another argument to account for the port value that determines which player is providing the coordinates.

notaz commented 10 months ago

256 0.158532 320 0.198166 384 0.226475 512 0.317065 640 0.396332

Looks like 1.58532 divided by pixel clock dividers 10, 8, 7, 5, 4 respectively.

It will just take some tinkering to find the right combination of values (enhanced resolution may need to be considered separately), but it's getting there!

You can use raw_w, raw_h, passed to vout_set_mode() to find the raw psx resolution.

I think psxScheduleIrq10() may need another argument to account for the port value that determines which player is providing the coordinates.**

That's not how it works. According to the docs there is only one irq line and it's shared on both controller ports. What seems to be done instead is the game enabling only one gun at a time in each frame. I've added pl_gun_byte2() function which is called when the game sends the enable byte to the gun. 0 probably means disable and 0x10 enable. I don't know if the enable affects buttons, probably it only enables the beam detection. Another issue is that the game sends the enable byte shortly after update_input() is called, so in update_input() you don't know which gun is enabled yet, meaning you probably have to move the irq10 code to pl_gun_byte2 I just added.

StormedBubbles commented 10 months ago

Thanks for this and for the explanations. I really appreciate it. This has been a good learning experience for me. I will make some adjustments after determining what I am doing wrong with this 2-player stuff 🙂.

I moved the IRQ10 portions of the code to pl_gun_byte2, and things still function as they did before. I tried moving the button inputs there too, but the buttons failed to work in that arrangement.

Am I supposed to manually influence the byte value within pl_gun_byte2 in libretro.c with those 0 and 0x10 values you mentioned based on the frame count being even or odd? A straight cut/paste of the IRQ10 code involving the coordinate scaling indicates I am only influencing the port variable. And am I supposed to call pl_gun_byte2 at some point later in libretro.c? I see that it gets used (and the byte parameter affected) in plugins.c.

Here is a more detailed breakdown of the behavior with 2 players. All 3 buttons (trigger, start, "back") on each gun work in all situations.

notaz commented 10 months ago

You're never supposed to call pl_gun_byte2, it's called by the emulator. byte tells you to enable the gun or not. The code should look something like this:

void pl_gun_byte2(int port, unsigned char byte)
{
  if ((byte & 0x10) && !(input_state_cb(port, ..._LIGHTGUN_IS_OFFSCREEN...
  {
    int gunx = input_state_cb(port, ...LIGHTGUN_SCREEN_X...
    int gunx_scaled = ...
    ...
    psxScheduleIrq10(irq_count, gunx_scaled, guny_scaled);
  }
}

Which game are you testing? At least from tracing Crypt Killer it looked like something like that should work. Other games may be acting differently, if pl_gun_byte2 isn't called that needs to be resolved first.

StormedBubbles commented 10 months ago

I was using Crypt Killer (NTSC) as a baseline, with some additional testing in Area 51 (NTSC). Your suggestion was exactly correct for those, and 2 guns can work simultaneously with those games now. Thanks!

Some friends and I will do some more testing over the next few days and give updates.

StormedBubbles commented 10 months ago

@Widge-5 and I tested every Justifier-compatible game we could find. I will list them with any special notes. Feel free to chime in if I missed any.

NTSC

PAL Most of these games are problematic. PAL versions of the above games fail to give proper coordinate alignment. The coordinates seem to be stuck in a right triangle on one side of the screen or are not recognized at all when games are in PAL mode. If forced to NTSC mode, some of them work fine. This happens regardless of whether I include checks in the coordinate formulas for PAL content. Affected games are PAL versions of any of the games above that have PAL versions as well as

notaz commented 10 months ago

PAL was not really implemented before, should be better now.

StormedBubbles commented 10 months ago

Thanks for that! Here is what we are seeing.

The remaining games (except for possible emulation issues with Judge Dredd) now work well regardless of region.

Widge-5 commented 10 months ago

I made a discovery today. Extreme Ghostbuster does not advertise compatibility with the Konami gun, it specifically refers to only synchronised light guns on the back of the case and in the manual, but Konami-type guns do work in this game. As I said before, I don't have an actual hyperblaster/justifer, but I do have a Logic3 Predator and a Cocoto funfair gun which has a Konami mode. Both guns worked with Extreme Ghostbusters on the PS1 and the alignment is better than with the guncon. I did test it briefly in StormedBubbles' in-progress impementation and it isn't working there just yet. The buttons all function but I get no shots landing on screen. Seems to be the same issue as the Moorhuhn games and Mighty Hits Special.

notaz commented 10 months ago

I've looked at this more and it seems some mess in the input code caused it to always respond to dualshock commands, even if the original digital pad or a gun is emulated. It seems this confused some games, so I changed it to ignore such commands. Mighty Hits Special started behaving better for me now.

The changed code affects every game so it would be good if someone tested if something didn't break, especially something like negcon emulation.

notaz commented 10 months ago

Judge Dredd: Justifiers ✅ but overall game compatibility in emulator ❌

It's a timing issue, it needs "PSX CPU Clock Speed" to be cranked up to work. I've pushed a change that does it automatically now.

StormedBubbles commented 10 months ago

Thanks so much again for all of this. I did some quick testing but will do more over the next few days and compare notes with friends.

✅After a quick first glance, Die Hard Trilogy 2 (NTSC), the Moorhen games (PAL), Mighty Hits Special (PAL), and Extreme Ghostbusters (PAL) appear to work properly now with the Justifier, including 2 players when applicable.

✅Gunfighter: The Legend of Jesse James also now appears to work properly with the Guncon, which resolves part of #573. I haven't checked the graphical issue with Point Blank in a long time but will follow up.

❌I will double-check if I accidentally used some wrong settings, but Judge Dredd (NTSC) is still locking up on my Raspberry Pi 4B maybe 30 seconds or so into gameplay. Is there anything I should watch out for in the core options that might interfere with that auto-adjustment you made? The options menu for me shows a clock speed of 57, which I believe is the default.

❌Star Wars (NTSC) also locks up on me when I try to calibrate with the Justifier in the options menu.

❌Some 989 games (NFL GameDay 2000, Twisted Metal III, Twisted Metal 4) have not ever worked with the multitap for me. That is still the case. Symptoms range from no control at all for controllers in the multitap (Twisted Metal) to just being able to skip some cutscenes (NFL GameDay 2000).

notaz commented 10 months ago

❌I will double-check if I accidentally used some wrong settings, but Judge Dredd (NTSC) is still locking up

Yes it seems to be still having issues with dynarec on and is not entirely stable with the interpreter. Looks like it'll require more work.

❌Star Wars (NTSC) also locks up on me when I try to calibrate with the Justifier in the options menu.

Somewhat works for me, although the accuracy is poor. Seems to only enable irq10 when the trigger is pushed. It's unclear how it's supposed to behave though.

❌Some 989 games (NFL GameDay 2000, Twisted Metal III, Twisted Metal 4) have not ever worked with the multitap

The multitap code was very patchy. I've cleaned it up quite a bit, you can try again.

StormedBubbles commented 10 months ago

@Widge-5 played through quite a bit of the PAL version of Judge Dredd without encountering any issues when using that recent auto-adjustment, so it helped there.

Just to clarify my experience with Star Wars: This is all with pl_gun_byte2 being used to send the coordinates for the NTSC USA version of disc 1 (CHD).

The multitap works now for those games (two multitaps for NFL GameDay 2000), and I closed the related issue. Thanks!

I saw among your changes an adjustment to the resolution variable for the Guncon. I haven't noticed any breaking effects of that, but it will require users to change some manual ratio/offset adjustments that they may already have saved. Some games are also apparently inaccurate on real hardware, which confuses things even more. EDIT: but if that's a more accurate formula, then heck yeah!

This is less evident with the Justifier. The scaling coefficients given by the docs appear to reproduce the exact behavior on real hardware (@Widge-5 has an extensive collection of these games and peripherals and has checked). The thing that we're not sure about is the default constant offset on each axis. Many of these games do not force calibration on the player (Die Hard Trilogy doesn't even have a calibration feature). All of the games I remember off the top of my head place the shots too low by default without calibration (overscan?), while the horizontal offset varies from some being too far left to others being too far right (an effect of the scaling coefficients creating ranges of 0 to some number less than 2146). Calibration eliminates that, but I guess some offset should be included in the formulas due to Die Hard Trilogy. The Moorhen/Moorhuhn games are something else. The alignment on real hardware is awful with the Guncon. It's better with the Justifier, but the scaling coefficient is different on the left/right sides of the screen.

notaz commented 10 months ago

I saw among your changes an adjustment to the resolution variable for the Guncon. I haven't noticed any breaking effects of that, but it will require users to change some manual ratio/offset adjustments that they may already have saved.

Actually that wrongly introduced resolution into the calculation, I've adjusted it again. About the user settings, we can't freeze all the progress just because of that, I think it's far more valuable to have good out-of-the-box experience. Anyway I don't plan to change it further so unless you want to change it too it's the last time the user settings break.

This is less evident with the Justifier. [...] All of the games I remember off the top of my head place the shots too low by default

Well you can subtract a constant from guny_scaled, if most games are helped by that it's a win.

while the horizontal offset varies from some being too far left to others being too far right

It might depend on the horizontal resolution. So like you have now justifier_multiplier you can add justifier_offset for each resolution and then add it to gunx_scaled.

The Moorhen/Moorhuhn games are something else. [...] the scaling coefficient is different on the left/right sides of the screen.

It doesn't look like that for me. Maybe it's caused by the fact that you are multiplying GunconAdjustX and GunconAdjustRatioY by all those other multipliers needlessly, you can just add them to gunx_scaled and guny_scaled as-is.

StormedBubbles commented 9 months ago

Thanks for all of the comments. I will try to tidy things up because there doesn't seem to be much more to do on the coordinates side. Is there an existing database with the resolutions of various games? I would just need to check that database or gather that info from the emulator to see if there happens to be an explicit relationship among the offset, resolution, and region like you mentioned could happen. In-game calibration for the games that have that feature (all but Die Hard Trilogy and Sporting Clays) fixes any default constant offsets, so I guess it's not super important to give every game a perfect default offset, but I figured, "Why not?" if there happens to be a way to explicitly calculate and correct it by default for the various resolutions.

StormedBubbles commented 9 months ago

After looking at a few games and noting the resolution given by the emulator, I'm not seeing a consistent default offset among the games with the same resolutions. I will note that I don't know of a proper way to check system mouse cursor vs. in-game shots with my Raspberry Pi. My lightgun has very good alignment with line of sight in other emulators that use absolute coordinates, so any observations are just based on that line of sight, but some Justifier games with the same resolutions as each other have default offsets that are way off from each other.

Perhaps in the future, a crosshair generated by the emulator would help? I once tried adapting one from another emulator to lr-pcsx-rearmed but quickly realized I didn't know what I was doing 😜. And maybe either creating a unique set of user-adjustable Justifier offset variables or renaming the current ones to "Lightgun Offset" instead of "Guncon Offset" could just allow users to adjust to their lines of sight, save the core options for that game, and not have to calibrate when playing in the future. The fact that a Guncon and a Justifier can be used together could cause a conflict between what numbers are needed for each variable per user, and while I know it's possible to use both devices simultaneously in the emulator now, that just seems like a rare fringe case (this is just a minor counter-argument to renaming the Guncon offset to Lightgun offset).

I still need to do cleanup (removing extraneous things that won't be used, looking at formatting), but I think I accounted for the enhanced resolution. It amounted to using vout_width / 2 and vout_height / 2 in all of the checks and calculations instead of just vout_width and vout_height if enhanced resolution is enabled.

My friends and I will spend some time over the weekend taking a look at the games some more.

notaz commented 9 months ago

I'm not seeing a consistent default offset among the games with the same resolutions

It might be there is none, it was just a guess.

a crosshair generated by the emulator

If you want to draw something on top of the game graphics vout_flip() is one place to do it.

It amounted to using vout_width / 2 and vout_height / 2

There are now psx_w and psx_h that you can use regardless of mode.

StormedBubbles commented 9 months ago

Thanks. I am finally looking at this again. I figure a good place to aim for and stop would be to add the (optional) crosshairs which correspond to the absolute position for each player, get a proper default offset for Die Hard Trilogy, and add Justifier-specific constant-offset core options for each axis separate from the Guncon ones since

For the crosshair, I chatted with an AI which more or less pointed me in the right direction. Inside of vout_flip(), and only when the specific player has a new core option for the crosshair enabled and is using either the Guncon or Justifier, I call this new function addCrosshair():

// Function to add a crosshair
void addCrosshair(int port, unsigned short *buffer, int bufferStride, int gunx, int guny, int size) {
   for (port = 0; port < 2; port++) {
      // Draw the horizontal line of the crosshair
      for (int i = gunx - size / 2; i <= gunx + size / 2; i++) {
         if (i >= 0 && i < bufferStride)
            buffer[guny * bufferStride + i] = 0xFFFF; // Set pixel to white (adjust as needed)
      }

      // Draw the vertical line of the crosshair
      for (int i = guny - size / 2; i <= guny + size / 2; i++) {
         if (i >= 0 && i < bufferStride)
            buffer[i * bufferStride + gunx] = 0xFFFF; // Set pixel to white (adjust as needed)
      }
    }
}

I just realized I still need to fix this to use the correct resolution variables mentioned above, but for the time being, what ends up being used in vout_flip() is:

int crosshairSize = 25;  // Adjust the size of the crosshair as needed
int gunx = (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X) + 32767.0f) * dstride / 65534.0f;
int guny = (input_state_cb(port, RETRO_DEVICE_LIGHTGUN, 0, RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y) + 32767.0f) * vout_height / 65534.0f - vout_height;
addCrosshair(port, dest, dstride, gunx, guny, crosshairSize);

Needing to subtract vout_height is confusing to me, but that's the only way with these variables that I got the tracking to work. The crosshairs for each player operate independently and track correctly around the whole game screen, but all I get is a horizontal line for each one when I was trying to draw a simple + symbol. There's no vertical line. I also tried

for (int i = (guny + vout_height) - size / 2; i <= (guny + vout_height) + size / 2; i++)

to account for the shift I performed, but I never see the vertical line in the +. I'm curious if I'm just making a dumb error somewhere. The AI suggested the buffer[i * bufferStride + gunx] = 0xFFFF; portion for the vertical line but got the horizontal line to appear with what it suggested there.

StormedBubbles commented 9 months ago

Ah, never mind. I found the issue with the crosshairs.

StormedBubbles commented 9 months ago

Other than one issue with the crosshairs and fixing any potential issues I may have introduced but didn't notice, I think I may have done all I can contribute and can submit this.

The only issue I notice with the crosshairs is that in some games, it gets "burned in" to some parts of the screen. For example, Die Hard Trilogy has a lot of dead space at the top of the screen that will leave copies of the crosshair if the user moves it through that area. They all go away eventually, but perhaps there is something in the code I could add to delete all of those residues? I'm not sure if those accumulating will cause a performance issue. I didn't notice anything of the sort on my Raspberry Pi 4 after some testing. Is that something that I can fix?

I did some minor manipulation of the position of the crosshair if it hits the right edge of the screen. Without that manipulation, part of the crosshair would warp to the left edge of the screen when the crosshair touched the right edge. Doing that manipulation doesn't appear to have a negative impact on the experience with a relative mouse, but perhaps solving the issue I mentioned above would help this also and I can remove that subtle manipulation.

The crosshair for each player can be activated and have its color changed independently (so one player can, say, use an absolute mouse without a crosshair while the other player uses a relative mouse with a crosshair in the color of his/her choice...or both players can use a crosshair of the same or different colors). I picked blue, red, green, and white as the selectable colors. The crosshair lines up with the absolute mouse position regardless of resolution.

StormedBubbles commented 9 months ago

In vout_flip(), this change removes the "ghosting" I saw with the crosshairs:

   memset(vout_buf_ptr, 0, dstride * vout_height * 2);

   if (vram == NULL || dims_changed)
   {
      // blanking
      if (vram == NULL)
         goto out;
   }

(i.e., memset gets moved outside of the condition). I think this clears the framebuffer at the beginning of each frame. It does indeed work for the crosshairs. The previous functionality is maintained while the ghosting is gone. However, I do not know if this negatively impacts anything else (or if that condition is even needed anymore).

notaz commented 9 months ago

Always clearing the buffer is wasting CPU time (draining people's batteries and contributing to global warming a tiny bit) because most of it is soon overwritten with the game's frame. You could keep the memset as-is and make it conditional - update the if statement like if (vram == NULL || dims_changed || cursor_enabled) to only do it when the cursor is active.

StormedBubbles commented 9 months ago

Thanks. That works.