Code archives/Graphics/VirtualGfx

This code has been declared by its author to be Public Domain code.

Download source code

VirtualGfx by BlitzSupport2011
BlitzMax's SetVirtualResolution call doesn't provide aspect ratio correction/black borders, so here's a simple copy-and-paste system for setting up a virtual graphics display, taking into account aspect ratio correction/black borders and also optional monitor stretch correction.

BASIC USAGE:

Note that the order is important!

1) Call InitVirtualGraphics before anything else;
2) Call Graphics as normal to create display;
3) Call SetVirtualGraphics to set virtual display size.

If you then hard-code your game for the virtual display size, it'll scale nicely on all systems.

ADDITIONAL:

To toggle full-screen monitor stretch during play, just call SetVirtualGraphics again with monitor_stretch set to True. Since you can't tell whether or not a monitor is stretching the display (eg. making a 640 x 480 screen wider to fit a 1920 x 1080 native resolution), you'll just have to provide this as an option to your users.

To change the actual Graphics mode, call EndGraphics as normal, then follow above steps.

I've tried to cover as much as possible, but there are probably situations (eg. calling SetOrigin/SetViewport yourself) where you'll have to restore VirtualGfx's custom origin/viewport afterwards. Not sure how much of a can of worms these would open up!

To modify an existing game 'should' only require calling InitVirtualGraphics in front of your current Graphics call, then SetVirtualGraphics afterwards; remember to do the same for any changes to the actual display resolution.

Here's an aspect-corrected version of RockOut, for instance; use F11 to toggle full-screen/windowed. (Executable only, hard-coded resolutions!)
SuperStrict

' -----------------------------------------------------------------------------
' USAGE: Note that the order is important!
' -----------------------------------------------------------------------------

' 1) Call InitVirtualGraphics before anything else;
' 2) Call Graphics as normal to create display;
' 3) Call SetVirtualGraphics to set virtual display size.

' The optional 'monitor_stretch' parameter of SetVirtualGraphics is there
' because some monitors have the option to stretch non-native ratios to native
' ratios, and you cannot detect this programmatically.

' For instance, my monitor's native resolution is 1920 x 1080, and if I set the
' Graphics mode to 1024, 768, it defaults to stretching that to fill the screen,
' meaning the image is stretched horizontally, so a square will appear non-
' square; however, it also provides an option to scale to the correct aspect
' ratio. Since this is set on the monitor, there's no way to detect or correct
' it other than by offering the option to the user. Leave it off if unsure...

' -----------------------------------------------------------------------------
' Copy this...
' -----------------------------------------------------------------------------

Type VirtualGfx

	Global VG:VirtualGfx
	
	Global DTInitComplete:Int = False
	Global DTW:Int
	Global DTH:Int

	Field vwidth:Int
	Field vheight:Int

	Field vxoff:Float
	Field vyoff:Float

	Field vscale:Float
	
	Function CreateVG:VirtualGfx (width:Int, height:Int)
		VG = New VirtualGfx
		VG.vwidth = width
		VG.vheight = height
	End Function
	
	Function SetVirtualGraphics (vwidth:Int, vheight:Int, monitor_stretch:Int = False)

		' InitVirtualGraphics has been called...
		
		If VirtualGfx.DTInitComplete
		
			' Graphics has been called...
			
			If GraphicsWidth () = 0 Or GraphicsHeight () = 0
				Notify "Programmer error! Must call Graphics before SetVirtualGraphics", True; End
			EndIf
			
		Else
			EndGraphics; Notify "Programmer error! Call InitVirtualGraphics before Graphics!", True; End
		EndIf

		' Reset of display needed when re-calculating virtual graphics stuff/clearing borders...
			
		SetVirtualResolution GraphicsWidth (), GraphicsHeight ()
		SetViewport 0, 0, GraphicsWidth (), GraphicsHeight ()
		SetOrigin 0, 0

		' Store current Cls colours...
		
		Local clsr:Int, clsg:Int, clsb:Int
		GetClsColor clsr, clsg, clsb
		
		' Set to black...
		
		SetClsColor 0, 0, 0
		
		' Got to clear both front AND back buffers or it flickers if new display area is smaller...
		
		Cls
		Flip

		Cls
		Flip
		
		Cls
		Flip
		
		' EDIT, 10 Nov 2011: I had a 3rd Cls/Flip here in case triple-buffering was enabled, but have finally
		' tested this and it wasn't needed. (Tested on NVIDIA GTX 260, Windows 7, with driver
		' version 275.33.)

		' EDIT 2, 30 Dec 2011: Reinstated 3rd Cls/Flip for triple-buffering; see first thread comment!

		' Put back Cls colours...

		SetClsColor clsr, clsg, clsb
		
		' Create new (global) virtual display object...
		
		VirtualGfx.CreateVG (vwidth, vheight)
		
		' Real Graphics width/height...
		
		Local gwidth:Int
		Local gheight:Int
		
		' If monitor is correcting aspect ratio IN FULL-SCREEN MODE, use desktop size, otherwise use
		' specified Graphics size. NB. This assumes user's desktop is using native monitor resolution,
		' as most laptops would be by default...
		
		If monitor_stretch And GraphicsDepth ()

			' Pretend real Graphics mode is desktop width/height...

			gwidth = DTW
			gheight = DTH

		Else

			' Use real Graphics width/height...

			gwidth = GraphicsWidth ()
			gheight = GraphicsHeight ()

		EndIf
		
		' Width/height ratios...
		
		Local graphicsratio:Float = Float (gwidth) / Float (gheight)
		Local virtualratio:Float = Float (VirtualGfx.VG.vwidth) / Float (VirtualGfx.VG.vheight)
		
		' Ratio-to-ratio. Don't even know what you'd call this, but hours of trial and error
		' provided the right numbers in the end...
		
		Local gtovratio:Float = graphicsratio / virtualratio
		Local vtogratio:Float = virtualratio / graphicsratio
		
		' Compare ratios...

		If graphicsratio => virtualratio
			
			' Graphics ratio wider than (or same as) virtual graphics ratio...

			VirtualGfx.VG.vscale = Float (gheight) / Float (VirtualGfx.VG.vheight)
			
			' Now go crazy with trial-and-error... ooh, it works! This tiny bit of code took FOREVER.
			
			Local pixels:Float = Float (VirtualGfx.VG.vwidth) / (1.0 / VirtualGfx.VG.vscale) ' Width after scaling
			Local half_scale:Float = (1.0 / VirtualGfx.VG.vscale) / 2.0

			SetVirtualResolution VirtualGfx.VG.vwidth * gtovratio, VirtualGfx.VG.vheight

			' Offset into 'real' display area...
			
			VirtualGfx.VG.vxoff = (gwidth - pixels) * half_scale
			VirtualGfx.VG.vyoff = 0
		
			' Set up virtual graphics area...
			
			SetViewport VirtualGfx.VG.vxoff, VirtualGfx.VG.vyoff, VirtualGfx.VG.vwidth, VirtualGfx.VG.vheight
			SetOrigin VirtualGfx.VG.vxoff, VirtualGfx.VG.vyoff
			
		Else
			
			' Graphics ratio narrower...
		
			VirtualGfx.VG.vscale = Float (gwidth) / Float (VirtualGfx.VG.vwidth)
			
			Local pixels:Float = Float (VirtualGfx.VG.vheight) / (1.0 / VirtualGfx.VG.vscale) ' Height after scaling
			Local half_scale:Float = (1.0 / VirtualGfx.VG.vscale) / 2.0
			
			SetVirtualResolution VirtualGfx.VG.vwidth, VirtualGfx.VG.vheight * vtogratio
		
			VirtualGfx.VG.vxoff = 0
			VirtualGfx.VG.vyoff = (gheight - pixels) * half_scale
		
			SetViewport VirtualGfx.VG.vxoff, VirtualGfx.VG.vyoff, VirtualGfx.VG.vwidth, VirtualGfx.VG.vheight
			SetOrigin VirtualGfx.VG.vxoff, VirtualGfx.VG.vyoff
			
		EndIf
	
	End Function

	Method VMouseX:Float ()
		Local mx:Float = VirtualMouseX () - vxoff
		If mx < 0 Then mx = 0 Else If mx > vwidth - 1 Then mx = vwidth - 1
		Return mx
	End Method
	
	Method VMouseY:Float ()
		Local my:Float = VirtualMouseY () - vyoff
		If my < 0 Then my = 0 Else If my > vheight - 1 Then my = vheight - 1
		Return my
	End Method
	
	Method VirtualWidth:Int ()
		Return vwidth
	End Method

	Method VirtualHeight:Int ()
		Return vheight
	End Method
	
End Type

' -----------------------------------------------------------------------------
' ... and these helper functions (required)...
' -----------------------------------------------------------------------------

Function InitVirtualGraphics ()

	' There must be a smarter way to check if Graphics has been called...
	
	If GraphicsWidth () > 0 Or GraphicsHeight () > 0 Then EndGraphics; Notify "Programmer error! Call InitVirtualGraphics BEFORE Graphics!", True; End

	VirtualGfx.DTW = DesktopWidth ()
	VirtualGfx.DTH = DesktopHeight ()

	' This only checks once... best to call InitVirtualGraphics again before any further Graphics calls (if you call EndGraphics at all)...
	
	VirtualGfx.DTInitComplete = True

End Function

Function SetVirtualGraphics (vwidth:Int, vheight:Int, monitor_stretch:Int = False)
	VirtualGfx.SetVirtualGraphics (vwidth, vheight, monitor_stretch)
End Function

Function VMouseX:Float ()
	Return VirtualGfx.VG.VMouseX ()
End Function

Function VMouseY:Float ()
	Return VirtualGfx.VG.VMouseY ()
End Function

' Don't need VirtualMouseXSpeed/YSpeed replacements!

Function VirtualWidth:Int ()
	Return VirtualGfx.VG.VirtualWidth ()
End Function

Function VirtualHeight:Int ()
	Return VirtualGfx.VG.VirtualHeight ()
End Function

' -----------------------------------------------------------------------------
' Don't copy after this...
' -----------------------------------------------------------------------------






' -----------------------------------------------------------------------------
' D E M O . . .
' -----------------------------------------------------------------------------

' REQUIRED! BEFORE Graphics!

InitVirtualGraphics

' Toggle for demo's windowed/full-screen switching...

Local FULLSCREEN_TOGGLE:Int = 0

' Change this if your monitor doesn't support it!

Local fullscreenwidth:Int = 1024
Local fullscreenheight:Int = 768
Local fullscreendepth:Int = 32

Local windowedwidth:Int = 640
Local windowedheight:Int = 400
Local windoweddepth:Int = 0

Graphics windowedwidth, windowedheight, windoweddepth

' If user's monitor is set to stretch to its native resolution, this needs to be set to True. I
' would recommend making this a selectable option in your game's configuration as there's no way
' to check this from any program...

Local monitor_adjusting:Int = False

' REQUIRED! AFTER InitVirtualGraphics!

SetVirtualGraphics 1280, 1024, monitor_adjusting

SetClsColor 64, 96, 128
SetMaskColor 255, 0, 255

' Some boxes...

Type Box
	Field x:Float, y:Float
	Field xs:Float, ys:Float
	Field size:Int
	Field r:Int, g:Int, b:Int
End Type

Local boxes:TList = CreateList ()

For Local loop:Int = 0 Until 100
	Local b:Box = New Box
	b.size = Rand (32)
	b.x = Rnd (VirtualWidth () - b.size)
	b.y = Rnd (VirtualHeight () - b.size)
	b.xs = Rnd (-4.0, 4.0)
	b.ys = Rnd (-4.0, 4.0)
	b.r = Rand (100, 255)
	b.g = Rand (100, 255)
	b.b = Rand (100, 255)
	ListAddLast boxes, b
Next

Repeat

	If FULLSCREEN_TOGGLE And KeyHit (KEY_RETURN)

		monitor_adjusting = 1 - monitor_adjusting
		SetVirtualGraphics 1280, 1024, monitor_adjusting

	EndIf
	
	If KeyHit (KEY_SPACE)

		FULLSCREEN_TOGGLE = 1 - FULLSCREEN_TOGGLE

		' Demo of how to change graphics mode...

		EndGraphics
		
		' I recommend calling this on the off-chance that the user's desktop size has changed...
		
		InitVirtualGraphics
		
		' Windowed/full-screen...
		
		If FULLSCREEN_TOGGLE
			Graphics fullscreenwidth, fullscreenheight, fullscreendepth
			SetVirtualGraphics 1280, 1024, monitor_adjusting
		Else
			Graphics windowedwidth, windowedheight, windoweddepth
			SetVirtualGraphics 1280, 1024
		EndIf
		
		' These get reset when exiting graphics mode...
		
		SetClsColor 64, 96, 128
		SetMaskColor 255, 0, 255
		
	EndIf
	
	Cls
	
	SetScale 1, 1

	For Local b:Box = EachIn boxes
		b.x = b.x + b.xs
		b.y = b.y + b.ys
		If b.x < 0 Or (b.x + b.size) > VirtualWidth () Then b.xs = -b.xs; b.x = b.x + b.xs
		If b.y < 0 Or (b.y + b.size) > VirtualHeight () Then b.ys = -b.ys; b.y = b.y + b.ys
		SetColor b.r, b.g, b.b
		DrawRect b.x, b.y, b.size, b.size
	Next
	
	SetColor 255, 0, 0
	DrawRect 0, 0, 16, 16
	DrawRect 0, VirtualHeight () - 16, 16, 16
	DrawRect VirtualWidth () - 16, 0, 16, 16
	DrawRect VirtualWidth () - 16, VirtualHeight () - 16, 16, 16
	
	SetColor 0, 0, 255
	DrawRect VMouseX () - 16, VMouseY () - 16, 32, 32
	
	SetScale 4, 4
	SetColor 255, 255, 255
	DrawText "SPACE: Windowed/full-screen", 20, 20
	If FULLSCREEN_TOGGLE Then DrawText "ENTER: Monitor stretch correction", 20, 60
	
	Flip
	
Until KeyHit (KEY_ESCAPE)

End

Comments

therevills2011
Thanks for this James :)

I've just tested it on my GeForce GTX 275 (Win 7 64bit using 285.62) and sometimes when I go between monitor_stretch states the "borders" flicker between black and what was there, so I added the removed cls; flip commands and it okays fine again :)

Within the Nvidia Control I have triple buffing on.


BlitzSupport2011
Ah, that's interesting... I'll put 'em back then! Thanks for letting me know.


therevills2015
To get this to work with SteamOverlay:




Code Archives Forum