Skip to content

Double-scanning support for VESA modes, accurate mode change logging, vga_draw.cpp rework & many other improvements #2654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Aug 3, 2023

Conversation

johnnovak
Copy link
Member

@johnnovak johnnovak commented Jun 29, 2023

Introduction

If it wasn't absolutely clear to everyone here that I'm a man who does not believe in half-measures, this PR should really drive that home! 😎 🚬

This started as something quite innocent: I only wanted to expand upon kcgen's excellent vgaonly work to add proper double-scanning to all VESA modes (see this ticket #2604) because it was annoying me that late DOS era demos that use high-colour modes appeared single-scanned... like an EGA monitor 😬 Plus I wanted to fix the logging on video mode changes so it logs the dimensions of the actual video mode, not the render buffer.

Sounds easy right? It turns out it was a case of having to take 10 steps back and take a quite different route just to be able to take one step forward. I discussed this with kcgen, his work was mostly based on heuristics and patching things up here and there, but that approach wasn't really maintainable and scalable; continuing down that track would've resulted in a huge unmaintainable mess. Still, without him bravely forging the path ahead, I would never have dared to touch the scary beast that is vga_draw.cpp!

So one thing lead to another, and suddenly I've sunk easily 150+ hours into this over the last month or so... Running literally thousands of test cases (see test cases below), fun stuff like that 😎 I'm still in recovery mode, refactoring this was exhausting...

To make sense half of all this, you need to be quite familiar with CGA/EGA/VGA... especially with VGA, the custom "Mode X" tweak mode stuff, and how double-scanning of VGA adapters affect image quality on real CRT monitors. Otherwise, you'll just need to trust me 😎 🤘🏻

I've tested things quite extensively over and over again, so I'm quite confident in these changes, but of course it's not impossible that some edge cases are broken. However, with the new, better structure in place, it will be much easier to fix problems with one specific video mode on one adapter without breaking random stuff on other adapters. That's not the case in current main...

Overview

Ok, so main changes in a nutshell (well, it's a rather large nut... 😅 🥜 )

REVIEW BY COMMIT IF YOU VALUE YOUR SANITY!!! I've put considerable effort in coming up with a relatively logical and clean history (I must have spent several full days just reorganising stuff with rebase...)

Double-scanning

  • The force_vga_single_scan setting has been moved to the [render] `section.

  • force_vga_single_scan can now be toggled at runtime and it gets activated on the next video mode change (which I find pretty cool).

  • All VESA modes supported by DOSBox are now double-scanned according to the mode table in int10_modes.cpp (tested with vesa_modes = all and vmemsize = 8). Naturally, the same goes for all non-VESA SVGA modes too on all supported SVGA adapters.

  • CGA, PCjr & Tandy text modes are now properly single-scanned.

  • The determination of whether a given VGA mode should be double-scanned or not should be almost perfectly correct now (I just don't dare tempt the devil by saying it's 100% 😈 🔥 ). This is because double-scanning is now controlled by the state of VGA registers for the most part instead of various image dimension and BIOS mode based heuristics. This has been tested with demos and various emulators (RealSpec, CCS64) that set up wacky custom modes to for example achieve a single-scanned look with around 300-lines of resolution at 85Hz+ refresh rates... Yep, all that works, in addition to all the games and demoscene productions that use strange custom modes on VGA!

Pixel-doubling

  • Sub-640 pixel wide video modes are always pixel-doubled now by default. This is needed to achieve more accurate CRT emulation with virtually all current CRT shaders in existence. For example, on a real monitor there should be zero difference between a 320x200 image displayed in a 320x200 video mode, or the same image width-doubled and displayed in a 640x200 mode. Without pixel-doubling, we would have a very noticeable difference with current CRT shaders that don't take such differences into account, resulting in horizontally blurrier image for the 320x200 video mode case (there are some image comparisons on this below).

  • A new force_no_pixel_doubling render setting is also available to disable this behaviour. This might be useful for optimising performance on low-powered devices, or to emulate a really blurry consumer CRT monitor or TV set.

  • See these threads for my discussions with prominent shader authors / people
    with an intense interest in accurate CRT emulation on the matter:

  • VileR was kind enough to confirm the above by taking some actual photos of his CGA monitor in 320x200 and 640x200 modes: https://www.vogons.org/viewtopic.php?p=1174367#p1174367

  • The TL;DR is that pixel-doubling of roughly sub-640 pixel wide content is necessary to achieve an authentic CRT look with currently available CRT shader technology, without having to maintain a set of different shaders and switch between them manually depending on whether you're playing a game that uses ~320 or ~640 pixel wide modes. Plus I find the multi-shader approach a non-solution when using DOSBox as a computer emulator because a shader optimised for ~320-pixel-wide content would look off in ~640-pixel-wide modes, and vice versa.

Shaders

  • Introduced two new shader pragmas, force_single_scan and force_no_pixel_doubling, so shaders can explicitly request single-scanning and no pixel-doubling if this would make no visual difference (we cannot determine this upstream, only the shader author can make this decision). The default "sharp" shader is such an example; this allows for finer scaling granularity with integer scaling enabled, and there are 4x less pixels to process by the shader which can make a big difference on low-powered devices.

  • The default interpolation/sharp shader has been updated to force single-scanning and no pixel-doubling. This ensures there's zero negative performance impact on people who are content with just using the defaults.

  • These pragmas also for cool possibilities, e.g. to make a "Commodore monitor" shader that forces single-scanning and no pixel-doubling.

Logging of current video mode

  • The removal of vgaonly broke the logging of the video mode dimensions because only the rendered image dimensions were available downstream, so we got things like MCGA 640x400 (mode 13h) in the logs which is super-confusing and incorrect. That is now fixed; e.g., for mode 13h we get VGA 320x200 (mode 13h) as we should.

  • The logging of video modes is now a lot more accurate in general; now we're consistently logging the video standard the mode first appeared in (e.g., CGA modes should be always CGA, regardless of the emulated display adapter in use).

  • If there's ambiguity regarding the video mode (e.g., the actual source image dimensions don't match the nominal dimensions of the BIOS video mode), we omit the (mode XYh) part from the logs to avoid confusing the user. The most noticeable side effect of this is that we no longer log weird stuff like MCGA 360x240 (mode 13h) which is clearly not mode 13h but a tweaked VGA mode---it's just mode 13h was used as a starting point to tweak the mode further. Stuff like that gets simply logged as VGA 360x240 now.

  • Another subtle but important detail: now the pixel aspect ratio of the video mode gets logged, not whatever pixel aspect ratio we derive from the rendered image's dimensions for rendering purposes.

  • Composite CGA/Tandy/PCjr modes in particular are being logged more accurately, and we get video mode change log message when toggling composite modes.

Image capture

If the below points don't make much sense, it's time to refresh your memory about the new enhanced & revamped image capture feature: #2503

  • Because now the dimensions and PAR of the video mode get propagated downstream, the image capturer can write the correct metadata tags into the images (this was just as incorrect as the logging of the current video mode's dimensions).

  • The raw image capture mode now really means raw; e.g., a 640x200 image isn't height-doubled like before but simply written as a 640x200 pixel-raster, end of story. This is the logical conclusion of the new revamped image capture functionality; the upscaled aspect-ratio preserving, "sharp" upscaled output is the default, this is what most people should use. The raw mode that corresponded to the "legacy" DOSBox screenshot capture functionality break compatibility with it on purse; this mode is really just there for special needs when you must capture the original, unaltered pixel-raster data.

  • The image capturer now always uses the dimensions of the video mode as the basis of performing the upscaling (as opposed to the dimensions of the rendered image, as done previously). In some cases (when dealing with "baked in" double-scanning) this can result in hitting the target ~1200px of upscaled vertical resolution better (because we can effectively use 2x finer vertical integer scaling steps for "baked-in" double-scanning).

  • Naturally, and following from the previous point, all combinations of pixel-doubling, "fake" double-scanning, real "baked-in" double-scanning are handled 100% correctly as you would expect during image capturing. Basically, the results for the same video mode should always be completely identical, regardless of these rendering side variations.

@johnnovak johnnovak force-pushed the jn/vga-double-scan-1 branch from be79227 to 6899da8 Compare June 29, 2023 03:53
@johnnovak johnnovak changed the title DRAFT, DON'T REVIEW!!! Jn/vga double scan 1 DRAFT, DON'T REVIEW!!! Jun 29, 2023
@johnnovak johnnovak force-pushed the jn/vga-double-scan-1 branch 28 times, most recently from 56c3c61 to 252b529 Compare July 4, 2023 12:45
This is a big rework that has been extensively tested on (almost) all
video modes supported by DOSBox (some are really hard to trigger because
they apparently are virtually not used by any program), and with a large
number of games and demoscene productions that use custom tweaked
resolutions.

Specifically:

- Untangle the current quite coupled way of performing these
  calculations and make calculations for the different modes largely
  independent of each other.

- Only group related modes where this does not hinder maintainabilitiy
  and ease of understanding. Previously, fixing something for one
  specific mode could easily break other modes -- that was an absolute
  nightmare to maintain.

- Remove the majority of heuristics-based and special-cased
  double-scanning decisions based on image dimensions and prefer to use
  bit flags in VGA registers that actually control the behaviour of VGA
  hardware.

- Same deal for pixel-doubling in 320/640 and 360/720 pixel wide modes
  and their tweaked variants.

- Same deal for aspect ratio calculations; all hardcoded values and most
  special-casing naturally fell away as the result of deriving the
  ratios either from display timings or the calculated image dimensions.

- Large number double-scanning and aspect ratio related fixes (e.g.
  CGA/Tandy/PCjr text modes were double-scanned which was wrong).

- Introduce a clear separation of the dimensions and pixel aspect ratio
  of the *video mode* vs the *rendered image*. Downstream processing
  often needs information about both (e.g. image capturing, logging
  details about the current video mode, viewport scaling, etc.)
  Previously, only the parameters of the rendered image were propagated
  downstream, and deriving the video mode's parameters from those is
  a bad and error-prone way of doing things at the very least, and
  impossible in some other cases (e.g. "baked in" double-scanning for VGA
  modes). Trying to patch the deficiencies up resulted in some hacks
  that were not even working very well.

- Add proper double-scanning and pixel-doubling support to all VESA
  modes, according to the INT 10h mode tables.

- Proper mode changes are now performed when toggling composite modes.
In addition to the currently stored rendered image dimensions and pixel
aspect ratio (PAR), also store the dimensions & PAR of the video mode.
This is necessary for logging the correct "nominal" pixel dimensions and
PAR of a given mode and storing them as metadata in the captured images.

This might also be handy later for certain OSD features that display
information about the current video mode.
- Improve VGA_GetCurrentMode() so the results reflect actual reality
  better.

- Always log the video standard the mode first appeared in (e.g. CGA
  modes should be always CGA, regardless of the display adapter), but
  don't use the name "MCGA" -- we don't even emulate the MCGA adapter,
  so this is confusing.

- Log the dimensions and pixel aspect ratio of the *video mode*, not the
  rendered image (this was super confusing when for example the 320x200
  256-colour 13h VGA mode got logged as 640x400)

- Don't log mode number when the actual dimensions don't equals the
  standard dimensions of the screen mode (this usually indicates a
  custom tweaked mode).
Previously, this used the rendered image's properties which resulted in
incorrect image dimensions being written into the metadata (e.g. 640x400
for mode 13h).
The image capturer now always uses the dimensions of the *video mode* as
the basis of performing the upscaling (as opposed to the dimensions of
the *rendered image*, as done previously). In some cases (when dealing
with "baked in" double-scanning) this can result in hitting the target
~1200px of upscaled vertical resolution better (because we can
effectively use 2x finer vertical integer scaling steps for "baked-in"
double-scanning).

An additional constraint has been introduced so we always use a 2:2
upscale factor at minimum, except for the 1600x1200 mode where we don't
upscale.

Also, the 'raw' image capture mode now always writes the unaltered
contents of the VGA framebuffer without any doublings applied (e.g.
640x200 now gets written as a raster of 640x200 pixels; previously, the
height was doubled to 400 pixels). This breaks compatibility with the
legacy DOSBox image capture behaviour *on purpose* -- the new default is
the aspect-ratio preserving 'upscaled' mode; 'raw' mode is supposed to
be only used in special circumstances when one wants to dump the raw,
unaltered pixel-data.
- Move 'force_vga_single_scan' setting to the [render] section

- 'force_vga_single_scan' can now be toggled at runtime

- Decouple the "double-scanning enabled" draw-state from forcing
  single-scanning
Sub-640 pixel wide video modes are always pixel-doubled now by default.
This is needed to achieve more accurate CRT emulation with current CRT
shaders. For example, on a real monitor there should be zero difference
between a 320x200 image displayed in a 320x200 video mode, or the same
image width-doubled and displayed in a 640x200 mode. Without
pixel-doubling, we would have a very noticeable difference with current
CRT shaders that don't take such differences into account (basically,
none of them do), resulting in horizontally blurrier image for the
320x200 video mode case.

A new 'force_no_pixel_doubling' render setting is also available to
disable this behaviour. This might be useful for optimising performance
on low-powered devices, or to emulate a really blurry consumer CRT
monitor or TV set.
Also instead of letting downstream processing override the PAR when
forcing square pixels (questionable), do that decision in vga_draw.cpp
and pass the appropriate PARs further down.
…ragmas

Shaders can now request single-scanning and no pixel-doubling with these
pragmas. The motivation for this can be to increase performance where
such doublings would make no visual difference (e.g. for our default
'interpolation/sharp' shader), or to achieve a certain look (e.g. to
emulate a consumer TV or an arcade or home computer monitor).
…ader

Double-scanning and pixel-doubling makes no visual difference to the
'sharp' shader, but results in 4x more pixels being processed.
Additionally, this allows for finer scaling steps when 'integer_scaling'
is enabled.
Previously, these settings were set on every video mode change which is
unnecessary. The way of setting these flags is also less convoluted now
(it's 100% driven by the renderer now).
@johnnovak johnnovak force-pushed the jn/vga-double-scan-1 branch from 3209cb4 to 7af1948 Compare August 3, 2023 02:38
@johnnovak johnnovak merged commit 4d7c322 into main Aug 3, 2023
@johnnovak
Copy link
Member Author

johnnovak commented Aug 3, 2023

OFF MY SHOULDER AT LAST! 😎

image

https://media.giphy.com/media/aNTmc4keX4Fva/giphy.gif

@kcgen
Copy link
Member

kcgen commented Aug 3, 2023

:shipit: 🚢 🤘

@kcgen kcgen deleted the jn/vga-double-scan-1 branch August 25, 2023 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or enhancement of existing features video Graphics and video related issues
Projects
No open projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants