drsly wrote on 2020-01-11, 15:08:
Working on VGA aspect of my PC emulator, but stuck on which register(s) determine 320 vs 640 pixels in horizontal.
These registers I understand:
- End Horizontal Display (3d4 index 01h) set to number of character clocks - 1. Controls how many pixels make up a 'scan line'. Most modes use 79, giving 640 or 720 pixels depending on 9/8 dot mode.
- Clocking Mode Register (3c4 index 01h) bit 0 - used to select between 720 and 640 pixel modes (or 360 and 320) and also is used to provide 9 bit wide character fonts in text mode.
- Offset (3d4 index 13h) - this field specifies the address difference between consecutive scan lines or two lines of characters. This register can be modified to provide for a virtual resolution, in which case Width is the width is the width in pixels of the virtual screen.
What I don't get is what selects between for instance 320 and 640 pixels.
From the documentation, I considered these a possibility:
- Clocking Mode Register (3c4 index 01h bit 3) - Dot Clock Rate. When this bit is set to 1, the master clock will be divided by 2 to generate the dot clock. All other timings are affected because they are derived from the dot clock. The dot clock divided by 2 is used for 320 and 360 horizontal PEL modes.
- CRTC Mode Control Register (3d4 index 17h bit 3) - Divide Memory Address clock by 2. When this bit is set to 1, the address counter uses the character clock input divided by 2. This bit is used to create either a byte or word refresh address for the display buffer.
The problem is neither of these bits are set under mode 13h (320 x 200). My guess is that there are other registers involved but which?
It's a bit more complicated than that. Most of those clocks don't affect each other as simple as just 640 or 720 pixels or double width or not.
There are different clocks (dot clocks, pixel clocks) that are affecting the output in different ways, all working in parallel(like a multicore CPU, each thread handling e.g. the dot clock or the pixel clock respectively.
Basically, there's a main clock that's generating the display signal(e.g. overscan, active display and blanking signal outputs).
Then, in parallel to the earlier clock, there's at least two other clocks(or based on the common clock) that determine the fetching and shifting of the pixels to the DAC.
And with advanced DACs, the DAC can have it's own 'clock'(derived from the dot clock) for higher pixel depth latching as well(e.g. 15/16-bit/32-bit color modes from 8BPP pixel inputs).
UniPCemu has this implemented already:
https://bitbucket.org/superfury/unipcemu/src/ … /vga_renderer.c
Look at the VGA_Renderer function for it's implementation(it starts out to check the display state(precalculated as horizontal/vertical timings precalcs for speed, see vga_precalcs.c and vga_crtcontroller.c for most of those).
Essentially, it starts with 3 basic parallelized steps(although executed serially):
1. Get the display state(the current scanline's current rendering pixel, taken from the precalcs). This is based on a horizontal and vertical counter for the virtual rendering clock in UniPCemu(it's CRTC precalcs). The result is a bitmask identifying different rendering states for the following stages(e.g. active display, blanking, retracing etc.)
2. Assign the current display and rendering signal(e.g. active display, overscan, blanking and retracing combined). This also handles stuff like retracing and horizontal/vertical total triggers.
3. Handle the current rendering scheme(active display or NOP).
The rendering scheme for overscan is simple, just render the color.
The active display generation is a bit more complicated(e.g. VGA_ActiveDisplay_Text):
- First, determine if we're to handle an active rendering clock or wait(see VGA_ActiveDisplay_timing(), which handles the ticking of the rendering scheme for the current mode. This basically takes the byte/word/doubleword modes for the rendering scheme into account, together with shifts etc.). It's states can be fount in vga_crtcontroller.h
- When rendering actively, first get the current pixel from the text/graphics mode pixel generator(from the latched values from VRAM and (in text mode only) the used font RAM).
- Then, check it against the attribute controller. If latched and not renderable yet(e.g. 8-bit(VGA and up)/16-bit pixel(SVGA only)) stop handling and don't render.
- Finally, when not latching, render the pixel(s), the attribute controller latching duplicating the pixels by some times, as well as the dot clock).
Basically, there's a dot clock(primary clock), and the character clock in turn is derived from the divided dot clock(secondary clock) in 8 or 9 clocks, of which in turn there's another clock derived(half character clock on e.g. ET4000's 16-bit color modes) for the VRAM timing(e.g. character/pixel fetching and latching from VRAM) for the Memory Address Counter and Video Load Rate.
Even more fun to be had: The character clock in SVGA modes(e.g. ET4000AX) can be 9 dots/clock, while the display rendering still needs to use 8 dots/clock(because it can't handle 9 pixels from 8 pixels of video RAM data).