PDA

View Full Version : Using DirectDraw's gamma control



Alimonster
05-11-2002, 09:28 PM
There seems to be a dearth of info about how to use the gamma control properly. I'm suggesting this method - if anyone has additional information, correction, or insights, please drop 'em here.

First of all, we have to know about the relevant interface, IDirectDrawGammaControl (http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/ddraw7/directdraw7/ddref_0myf.asp).
This lets us control the gamma (in effect fading to black/back again easily, or removing specific parts from the gamma ramp to change the screen's colour, maybe giving a yellow tint, for example).

The first step is to work on assumption that we have a class encapsulating your basic DDraw screen (with flip, draw, and so on). I'm assuming this solely because I have such a class, making this post easier via copy-and-paste :).

Our basic class might look like this:

type
TDXScreen = class
private
FSupportsGamma: Boolean;
FOldGamma: TDDGammaRamp;
procedure CheckForGamma;
// other stuff
public
// stuff
property GammaAvailable: Boolean read FSupportsGamma;
procedure SetGamma(RedPercent, GreenPercent, BluePercent: Double);
procedure RestoreGamma;
end;
First of all, notice the TDDGammaRamp structure. A gamma ramp record stores values (256 of 'em) for the gamma ramp. Each part of this record is split into 3 sections for red, green and blue. If you alter any of those, you can change that colour in the gamma ramp. If we reduce the colours for the entire ramp, we can lighten or darken the gamma.

Here's my CheckForGamma procedure, which tries to figure out whether the device supports gamma controls (which isn't a given!):

//
// CheckForGamma
//
// Ensure that the gamma ramp is available for use by checking the IDirectDraw7
// interface's caps
//
procedure TDXScreen.CheckForGamma;
var
Caps: TDDCaps;
Gamma: IDirectDrawGammaControl;
begin
FSupportsGamma := False;

// init our capabilities record to zero
FillChar(Caps, SizeOf(Caps), 0);
Caps.dwSize := SizeOf(CapS);

// is it supported?
if FDD7.GetCaps(@Caps, nil) = DD_OK then
begin
FSupportsGamma := Caps.dwCaps2 and DDCAPS2_PRIMARYGAMMA <> 0;

// if it is supported, grab the old value as a reference for other gammas
if FSupportsGamma then
begin
FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.GetGammaRamp(0, FOldGamma);
end;
end;

{$IFDEF LOG_FILE}
if FSupportsGamma then WriteLog('SUPPORTS gamma fades!')
else WriteLog('DOES NOT SUPPORT gamma fades');
{$ENDIF}
end;
This looks complicated but it's not really. First of all, as with all DDraw things, we need to declare a record to store some info. The DDraw interfaces allow us to retrieve info by querying capabilities - we're asking "does it support feature X" by using the GetCaps function. DDraw fills out the passed info with the wanted info (i.e., it does or it doesn't support gamma, in this case).

We can only use gamma on the primary surface. Thus, after the GetCaps call, we check the returned capabilities. If the bit-mask for DDCAPS2_PRIMARYGAMMA is set (you can learn about bit-fiddling over at my site here (http://www.alistairkeys.co.uk/bits.shtml)) then we're in luck!

Next, we store the current gamma value. It's no use fiddling with the gamma if we end up finishing the program in darkness - the user won't be very pleased, that's for sure! We grab the old value for reference later on.

It's possible to get the gamma interface using QueryInterface. I believe (and please correct me if I'm wrong) that you don't have to clear up the interface returned, since it will die when it goes out of scope).

The returned value is an IDirectDrawGammaControl. This, handily enough, gives us access to the current gamma ramp! We then call its GetGammaRamp function.

At this point, assuming everything's worked, we know that the device supports gamma and what its starting value is. How do we use this in practise?

I came up with this cunning plan - any gamma we set will be in reference to the original gamma. We can deal with percent (100% will be the original gamma value, 0% will be total darkness). This makes sense to me - after all, there's no reason override the user's settings here :). We would also use overbright values (> 100%), I suppose, though that's pushing it a little.

Here are the two functions for us now. Note that RestoreGamma is simply a helper to set the gamma back to the default (100%):

//
// Clamp
//
// Ensures the value is between the min and max passed in
//
procedure Clamp(var Value: Double; const Min, Max: Double);
begin
if Value < Min then Value := Min;
if Value > Max then Value := Max;
end;

//
// SetGamma
//
// Sets the gamma ramp to the specified amounts, which are percents (0 - 100)
// for each colour. This effects the overall brightness of the screen (note:
// 100 percent is fully on, 0 percent is off)
//
procedure TDXScreen.SetGamma(RedPercent, GreenPercent, BluePercent: Double);
var
NewGamma: TDDGammaRamp;
i: Integer;
Gamma: IDirectDrawGammaControl;
begin
// you can remove this if you want...
Clamp(RedPercent, 0, 100);
Clamp(GreenPercent, 0, 100);
Clamp(BluePercent, 0, 100);

if FSupportsGamma then
begin
for i := 0 to 255 do
begin
// todo speed this up?
NewGamma.red[i] := Trunc((FOldGamma.Red[i] * RedPercent) / 100);
NewGamma.green[i] := Trunc((FOldGamma.Green[i] * GreenPercent) / 100);
NewGamma.blue[i] := Trunc((FOldGamma.Blue[i] * BluePercent) / 100);
end;

FPrimary.QueryInterface(IID_IDirectDrawGammaContro l, Gamma);
Gamma.SetGammaRamp(0, NewGamma);
end;
end;

//
// RestoreGamma
//
// Resets the gamma ramp to the default
//
procedure TDXScreen.RestoreGamma;
begin
SetGamma(100, 100, 100);
end;

The above is fast enough. It won't be your bottleneck, that's for sure (well, that's the case for me since I like l33t pixel manipulation code, which runs over millions of pixels ;)).

Speed tip 1: Trunc is *very very slow*. If possible, use round instead!
Speed tip 2: You could remove those divisions outside of the loop (calculate RedPercent / 100 outside the loop, for example).

Corrections? Clarifications? Miscellaneous threats? Post them here, please.

If anyone wants to investigate this for DelphiX, please do. I'm not sure if it supports gamma out-of-the-zip. If it doesn't, this should come in handy. If nobody takes up the challenge then I may just do so later on.

Marty
05-11-2002, 09:48 PM
but how do you achieve a gamma control in dx8? a IDirectDrawGammaControl-Interface doesn't exists in dx8!? :roll:

Alimonster
05-11-2002, 10:03 PM
If you're cheap then just draw an alpha-blended poly over the screen ;) *cough cough* :P

A quick nosey through the DX SDK showed up seemingly analagous stuff:

D3DGAMMARAMP (I'd imagine that would be prefixed with 'T', TD3DGammaRamp)
IDirect3DDevice8::GetGammaRamp
IDirect3DDevice8::SetGammaRamp

[EDIT: Doh!] Looking at the wrong thing! Device calibration is totally unnecessary here. Ignore that quote!

Anyway, you'd probably use IDirect3DDevice8.GetDeviceCaps, passing in a TD3DCaps8 structure. You'd check that TD3DCaps8's caps2 for the flag D3DCAPS2_FULLSCREENGAMMA, plus probably some other caps. I've not used D3D so I can't say for sure.

Bobby
07-11-2002, 05:28 AM
Hi, I would go with drawing the polygon over the screen ;) Its fast, and you could make it any color you wanted to.

Marty
07-11-2002, 04:58 PM
yes, maybe this is the best solution

savage
12-11-2002, 10:39 AM
I managed to get some DirectX stuff included on the Delphi 7 Companion CD that included a demo that shows how to use the gamma control interface. If you did not buy Delphi 7 and still want the package ( which als includes other DirectDraw, Direct3D and DirectMusic demos ), it can be download from Code Central @
http://codecentral.borland.com/codecentral/ccweb.exe/listing?id=18611

Clootie
15-11-2002, 06:37 AM
Hi, I would go with drawing the polygon over the screen Its fast, and you could make it any color you wanted to.Tell it to Carmak! :twisted:
Do you know what Quake3 engine uses special gamme correction table for "overbright" lighting? And Gamme correction is indeed is higher precision.


Corrections? Clarifications? Miscellaneous threats? Post them here, please. Some drivers (incorrectly) return only BYTE filled precision - i.e. only lower byte is meaningfull with higher set to zero - when called by GetGammeRamp, but assume what you pass 16it - WORD filled gamma ramp table.

erikphilips
16-11-2003, 09:01 PM
Hi, I would go with drawing the polygon over the screen Its fast, and you could make it any color you wanted to.Is there an example of this somewhere so that I may learn how to do this?

So is there a definite answer on this or is up to the discretion of the programmer? I guess I could play with both.

Alimonster, at what part of my code should i be calling SetGamma? Should I call it before the Clear, after the clear/before the blts, after the clear/after the blts, or after the flip?