{****************************************************************************}
{Unit VENOMGTF - it is a helper unit for graphics library VenomGFX.          }
{It is not intended to be called by user, because it is used internaly when  }
{setting videomode with specified monitor refresh frequency                  }
{      written by Rayer, translated into pascal and adjusted by Laaca        }
{****************************************************************************}

unit venomgtf;

interface

Procedure Calc_crtc_timing(mode,xr,yr:dword;freq:byte; var crtc_buffer);


const
crtc_want_margins:boolean = false;
crtc_want_interlace:boolean = false;
crtc_want_doublescan:boolean = true;{ma vliv jen na mody s mene nez 400 radky}

implementation
uses Dos;

type

CRTC_vesa_Block = packed record
    HorizontalTotal:word;
    HorizontalSyncStart:word;
    HorizontalSyncEnd:word;
    VerticalTotal:word;
    VerticalSyncStart:word;
    VerticalSyncEnd:word;
    Flags:byte;
    PixelClock:dword;    { units of Hz }
    RefreshRate:word;    { units of 0.01 Hz }
    reserved:array[0..39] of byte;
end;

GTF_hCRTC = record
    hTotal:longint;
    hDisp:longint;
    hSyncStart:longint;
    hSyncEnd:longint;
    hFrontPorch:longint;
    hSyncWidth:longint;
    hBackPorch:longint;
end;

GTF_vCRTC = record
    vTotal:longint;                    {vertical total}
    vDisp:longint;                     {vertical displayed}
    vSyncStart:longint;                {vertical sync start}
    vSyncEnd:longint;                  {vertical sync end}
    vFrontPorch:longint;               {vertical front porch}
    vSyncWidth:longint;                {vertical sync width}
    vBackPorch:longint;                {vertical back porch}
end;


{Define the main structure for holding generated GTF timings}
GTF_timings = packed record
    h:gtf_hcrtc;                       {horizontalni CRTC parametry}
    v:gtf_vcrtc;                       {vertikalni CRTC parametry}
    hSyncPol:boolean;                  {horizontalni sync. polarita}
    vSyncPol:boolean;                  {vertikalni sync. polarita}
    interlace:boolean;                 {interlace ano/ne}
    doublescan:boolean;                {doublescan ano/ne}
    vFreq:double;                      {vertikalni frekvence (Hz)}
    hFreq:double;                      {horizontalni frekvence (KHz)}
    dotClock:double;                   {Pixel clock (MHz)}
end;


{Define the structure for holding standard GTF formula constants}
GTF_constants = record
  margin:double;                    {jake % obrazovky zabira ramecek}
  cellGran:double;                  {granularita bunky textoveho znaku}
  minPorch:double;                  {Minimum front porch in lines/chars}
  vSyncRqd:double;                  {Width of V sync in lines}
  hSync:double;                     {Width of H sync as percent of total}
  minVSyncBP:double;                {Minimum vertical sync + back porch (us)}
  m:double;                         {Blanking formula gradient}
  c:double;                         {Blanking formula offset}
  k:double;                         {Blanking formula scaling factor}
  j:double;                         {Blanking formula scaling factor weight}
end;

const
GTF_lockVF  = 1;      {lock to vertical frequency}
GTF_lockHF = 2;       {lock to horizontal frequency}
GTF_lockPF  = 3;      {Lock to pixel clock frequency}

{------------------------- Global Variables --------------------------------}
gc:GTF_constants = (margin:1.8; cellgran:8; minPorch:1; vSyncRQD: 3;
                    hSync: 8; minVSyncBP:550; m:600; c:40; k:128; j:20);


Function Round(v:real):longint;
var l:longint;
begin
l:=Trunc(v+0.5);
if l<0 then Round:=l-1 else Round:=l;
end;


Procedure GetInternalConstants(var c:GTF_constants);
{ Vypocita celociselne zaokrouhlenou sadu GTF konstant. Tyto konstanty se lisi
  od skutecnych GTF konstant, ktere mohou byt nastaveny pro monitor.}
begin
c.margin:=GC.margin;
c.cellgran:=round(gc.cellGran);
c.minPorch:=round(gc.minPorch);
c.vSyncRQD:=round(gc.vSyncRQD);
c.hsync:=GC.hsync;
c.minVSyncBP:=GC.minvsyncBP;
if GC.k = 0 then c.k:=0.001 else c.k:=GC.k;
c.m:= (c.k/256) * GC.m;
c.c:= (GC.c - GC.j) * (c.k/256) + GC.j;
c.j:=GC.j;
end;


Function boby(b:boolean):byte;
begin
if b then boby:=1 else boby:=0;
end;


Procedure GTF_calcTimings(hPixels,vLines:double;freq:double;typ:byte;
               wantmargins,wantinterlace,wantdouble:boolean;var t:GTF_timings);
{Parameters:  hPixels  - X resolution
              vLines   - Y resolution
              freq     - Frequency (Hz, KHz or MHz depending on type)
              type     - 1 - vertical, 2 - horizontal, 3 - dot clock
              margins  - True if margins should be generated
              interlace - True if interlaced timings to be generated
              t        - Place to store the resulting timings

Description:  Calculates a set of GTF timing parameters given a specified
              resolution and vertical frequency. The horizontal frequency
              and dot clock will be automatically generated by this
              routines.

              For interlaced modes the CRTC parameters are calculated for
              a single field, so will be half what would be used in
              a non-interlaced mode.}

var
vFieldRate, hPeriod:double;
topMarginLines, botMarginLines:double;
leftMarginPixels, rightMarginPixels:double;
hPeriodEst, vSyncBP, vBackPorch:double;
vTotalLines, vFieldRateEst:double;
hTotalPixels, hTotalActivePixels, hBlankPixels:double;
idealDutyCycle,hSyncWidth,hSyncBP,hBackPorch:double;
idealHPeriod:double;
dotclock,vFreq,hFreq:double;
c:GTF_constants;
interlace:boolean;

begin
idealDutyCycle:=0;
vTotalLines:=0;
hPeriodEst:=0;
vSyncBP:=0;
vBackPorch:=0;
hPeriod:=0;


GetInternalConstants(c);
{Get rounded GTF constants used for internal calculations}

vFreq:=freq;
hFreq:=freq;
dotClock:=freq;
{Move input parameters into appropriate variables}

hPixels:=round(hPixels / c.cellGran) * c.cellGran;
{Round pixels to character cell granularity}


vFieldRate:=vFreq;
interlace:=false;
if (wantInterlace) then dotClock:=dotClock*2;
{For interlaced mode halve the vertical parameters, and double the required
 field refresh rate.}

if wantdouble and (vlines<400) then
   begin
   t.doublescan:=true;
   vLines:=vLines*2;
   end
   else t.doublescan:=false;

if (wantMargins) then {Determine the lines for margins}
   begin
   topMarginLines:=round(c.margin / 100 * vLines);
   botMarginLines:=round(c.margin / 100 * vLines);
   end
   else begin
   topMarginLines:=0;
   botMarginLines:=0;
   end;

  if typ<>GTF_lockPF then
     BEGIN
     if typ=GTF_lockVF then
        begin
        hPeriodEst:=((1/vFieldRate)-(c.minVSyncBP/1000000))/(vLines+(2*topMarginLines)+c.minPorch+boby(interlace))*1000000;
        {Estimate the horizontal period}
        vSyncBP:=round(c.minVSyncBP / hPeriodEst);
        {Find the number of lines in vSync + back porch}
        end

     else if typ=GTF_lockHF then
        vSyncBP:=round((c.minVSyncBP * hFreq) / 1000);
        {Find the number of lines in vSync + back porch}


     vBackPorch:=vSyncBP - c.vSyncRqd;
     {Find the number of lines in the V back porch alone}


     vTotalLines:=vLines + topMarginLines + botMarginLines + vSyncBP + boby(interlace) + c.minPorch;
     {Find the total number of lines in the vertical period}

     if typ=GTF_lockVF then
        begin
        vFieldRateEst:=1000000 / (hPeriodEst * vTotalLines);
        {Estimate the vertical frequency}

        hPeriod:=(hPeriodEst * vFieldRateEst) / vFieldRate;
        {Find the actual horizontal period}

        vFieldRate:=1000000 / (hPeriod * vTotalLines);
        {Find the actual vertical field frequency}
        end
        else if typ=GTF_lockHF then vFieldRate:=(hFreq / vTotalLines) * 1000;
                {Find the actual vertical field frequency}
     END; {typ<>GTF_lockPF}

  if wantMargins then {Find the number of pixels in the left and right margins}
     begin
     leftMarginPixels:=round(hPixels * c.margin) / (100 * c.cellGran);
     rightMarginPixels:=round(hPixels * c.margin) / (100 * c.cellGran);
     end
     else begin
     leftMarginPixels:=0;
     rightMarginPixels:=0;
     end;

  hTotalActivePixels:=hPixels + leftMarginPixels + rightMarginPixels;
  {Find the total number of active pixels in image + margins}

  if typ=GTF_lockVF then idealDutyCycle:=c.c - ((c.m * hPeriod) / 1000)
     {Find the ideal blanking duty cycle}

  else if typ=GTF_lockHF then idealDutyCycle:=c.c - (c.m / hFreq)
     {Find the ideal blanking duty cycle}

  else if typ=GTF_lockPF then
     begin
     idealHPeriod:=(((c.c-100)+(sqrt(((100-c.c)*(100-c.c))+(0.4*c.m*(hTotalActivePixels+
                   rightMarginPixels+leftMarginPixels)/dotClock))))/(2*c.m))*1000;
     {Find ideal horizontal period from blanking duty cycle formula}
     idealDutyCycle:=c.c - ((c.m * idealHPeriod) / 1000);
     {Find the ideal blanking duty cycle}
     end;

  hBlankPixels:=round((hTotalActivePixels * idealDutyCycle) / ((100 - idealDutyCycle) * c.cellGran)) * c.cellGran;
  {Find the number of pixels in blanking time}
  hTotalPixels:=hTotalActivePixels + hBlankPixels;
  {Find the total number of pixels}
  hBackPorch:=round((hBlankPixels / 2) / c.cellGran) * c.cellGran;
  {Find the horizontal back porch}
  hSyncWidth:=round(((c.hSync/100) * hTotalPixels) / c.cellGran) * c.cellGran;
  {Find the horizontal sync width}
  hSyncBP:=hBackPorch + hSyncWidth;
  {Find the horizontal sync + back porch}

  if typ=GTF_lockPF then
     begin
     hFreq:=(dotClock / hTotalPixels) * 1000;
     {Find the horizontal frequency}
     vSyncBP:=round((c.minVSyncBP * hFreq) / 1000);
     {Find the number of lines in vSync + back porch}
     vBackPorch:=vSyncBP - c.vSyncRqd;
     {Find the number of lines in the V back porch alone}
     vTotalLines:=vLines + topMarginLines + botMarginLines + vSyncBP
        + boby(interlace) + c.minPorch;
     {Find the total number of lines in the vertical period}
     vFieldRate:=(hFreq / vTotalLines) * 1000;
     {Find the actual vertical field frequency}
     end
     else begin
     if typ=GTF_lockVF then hFreq:=1000 / hPeriod else
      {Find the horizontal frequency}
     if typ=GTF_lockHF then hPeriod:=1000 / hFreq;
      {Find the horizontal frequency}

     dotClock:=hTotalPixels / hPeriod;
     {Find the pixel clock frequency}
     end;


  t.vFreq:=vFieldRate;
  t.hFreq:=hFreq;
  t.dotClock:=dotClock;
  {Return the computed frequencies}

  t.h.hTotal:=Trunc(hTotalPixels);
  t.h.hDisp:=Trunc(hTotalActivePixels);
  t.h.hSyncStart:=t.h.hTotal - Trunc(hSyncBP);
  t.h.hSyncEnd:=t.h.hTotal - Trunc(hBackPorch);
  t.h.hFrontPorch:= t.h.hSyncStart - t.h.hDisp;
  t.h.hSyncWidth:=Trunc(hSyncWidth);
  t.h.hBackPorch:=Trunc(hBackPorch);
  {Determine the horizontal timing parameters}

  t.v.vTotal:=Trunc(vTotalLines);
  t.v.vDisp:=Trunc(vLines);
  t.v.vSyncStart:=t.v.vTotal - Trunc(vSyncBP);
  t.v.vSyncEnd:=t.v.vTotal - Trunc(vBackPorch);
  t.v.vFrontPorch:=t.v.vSyncStart - t.v.vDisp;
  t.v.vSyncWidth:=Trunc(c.vSyncRqd);
  t.v.vBackPorch:=Trunc(vBackPorch);
  {Determine the vertical timing parameters}

  if wantInterlace then
     begin {Halve the timings for interlaced modes}
     t.v.vTotal:=t.v.vTotal div 2;
     t.v.vDisp:=t.v.vDisp div 2;
     t.v.vSyncStart:=t.v.vSyncStart div 2;
     t.v.vSyncEnd:=t.v.vSyncEnd div 2;
     t.v.vFrontPorch:=t.v.vFrontPorch div 2;
     t.v.vSyncWidth:=t.v.vSyncWidth div 2;
     t.v.vBackPorch:=t.v.vBackPorch div 2;
     t.dotClock:=t.dotClock / 2;
     end;

  {Mark as GTF timing using the sync polarities}
  t.interlace:=wantInterlace;
  t.hSyncPol:=false;
  t.vSyncPol:=true;
end;


Procedure GTF_getConstants(var c:GTF_constants);
begin
c:=gc;
end;

Procedure GTF_setConstants(c:GTF_constants);
begin
GC:=c;
end;


Procedure get_closest_pixel_clock(mode_no:dword;var vclk:dword);
{Uses VESA 3.0 function 0x4F0B to find the closest pixel clock to the
 requested value.}
var r:registers;
begin
r.ax:=$4f0B;
r.bl:=0;
r.ecx:=vclk;
r.dx:=mode_no;
intr($10,r);
if r.ah<>0 then vclk:=0 else vclk:=r.ecx;
end;



Procedure Calc_crtc_timing(mode,xr,yr:dword;freq:byte; var crtc_buffer);
var c:^crtc_vesa_block;
    gtf:GTF_timings;
    dotclock:dword;

begin
c:=@crtc_buffer;
FillChar(c^,sizeof(crtc_buffer),0);
GTF_calcTimings(xr,yr,freq,GTF_lockVF,crtc_want_margins,crtc_want_interlace,crtc_want_doublescan,gtf);
{Na zaklade pozadovane vertikalni frekvence spocitam CRTC parametry}


dotclock:=Trunc(gtf.dotclock*1000000);
Get_Closest_Pixel_Clock(mode,dotclock);
{Z hardware zjistim nejblizsi dostupnou pixelovou frekvenci}

if dotclock<>0 then
   GTF_calcTimings(xr,yr,dotclock/1000000,GTF_lockPF,crtc_want_margins,crtc_want_interlace,crtc_want_doublescan,gtf);
   {A udelam zpetny prepocet, tzn. upresneni CRTR parametru}


c^.HorizontalTotal:=gtf.h.hTotal;
c^.HorizontalSyncStart:=gtf.h.hSyncStart;
c^.HorizontalSyncEnd:=gtf.h.hSyncEnd;
c^.VerticalTotal:=gtf.v.vTotal;
c^.VerticalSyncStart:=gtf.v.vSyncStart;
c^.VerticalSyncEnd:=gtf.v.vSyncEnd;
c^.PixelClock:=dotclock;
c^.RefreshRate:=Trunc(gtf.vFreq*100);
{Hodnoty vlozim do VESA crtc bloku}

if gtf.hSyncPol then c^.Flags:=0 else c^.Flags:=4;
if not gtf.vSyncPol then c^.Flags:=c^.Flags or 8;
if gtf.doublescan then c^.Flags:=c^.Flags or 1;
if crtc_want_interlace then c^.Flags:=c^.Flags or 2;
end;


end.
