Screen Fading

BlitzPlus Forums/BlitzPlus Programming/Screen Fading

Murilo(Posted 2004) [#1]
What is the best/fastest way to cross fade two 640x480 images?

I've written my own set of functions that use banks and the lockedpixels functionality of B+. It's a brute force approach and runs fine on most systems. However, users with lower spec'd machines/graphics cards are reporting sloooow fades, and I'd like to address this.

So, is there a sure-fire way of cross fading that'll work well on all machines?


Rottbott(Posted 2004) [#2]
Try using the gamma. It's not really meant for this but it's very fast.


Rob Farley(Posted 2004) [#3]
There's no easy way to do it. Brute force is the only way.

You might find you get better performance if you put the RGB values seperate into banks before you start your fade loop. This might mean you get a bit of a stutter before it starts.

However, this would mean no readpixelfast at run time. Then all you're going to be doing is 6 readbytes from a bank (r1, g1, b1 and r2, g2, b2) and one writepixelfast per pixel.

This isn't tested, just a though.


Murilo(Posted 2004) [#4]
Thought as much - Thanks guys.

>You might find you get better performance if you put the RGB values seperate into banks before you start your fade loop.

That's exactly what I do now, and it works just great on the majority of PCs. As you say, there's a momentary stutter and a temporary increase in memory usage, but it works well. I'll post my code here in case I've missed some obvious optimisations (and others may find it useful?).

Cheers


Murilo(Posted 2004) [#5]
; Title:	640x480 full-screen fade
; Version:	4.01 (09/Sep/2004)
; Author:	Leigh Bowers

;Graphics 640, 480, 16, 2
;img1% = LoadImage("Themes\Evolution\640x480\Images\Loading.bmp")
;img2% = LoadImage("Archon001.bmp") ; CreateImage(640, 480)
;WaitKey
;lngStart% = MilliSecs()
;ScreenFade img1

;WaitKey
;ScreenFade img2, 0.05
;ScreenFade 0, 0.1, $ff, $ff, $ff
;ScreenFade
;WaitKey

Function ScreenFade(pimgDestinationImage% = 0, pdblFadeSpeed# = 0.1, ColourR% = 0, ColourG% = 0, ColourB% = 0, pimgSourceImage% = 0)

	If Prefs\Debug Then Return

	lngBank% = CreateBank(2 * (307200 * 3))

; Take a copy of the front buffer and the destination screen

	If pimgSourceImage = 0 Then
		SetBuffer FrontBuffer()
	Else
		SetBuffer ImageBuffer(pimgSourceImage)
	End If

	lngBankPos% = 0

	For bytLoop% = 0 To 1
	
		If bytLoop = 1 Then
			SetBuffer BackBuffer()
			If pimgDestinationImage = 0 Then
				ClsColor ColourR, ColourG, ColourB : Cls ; Fading to a blank screen (of specified colour)
			Else
				DrawBlock pimgDestinationImage, 0, 0 ; The image we're cross-fading in to
			End If
		End If
		
		LockBuffer
		bnkScreenFade% = LockedPixels()
		intPitch% = LockedPitch()
		bytFormat% = LockedFormat()
		lngOffset% = 0
		
		Select bytFormat

			Case 1			
				intY% =0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						lngPixel% = PeekShort(bnkScreenFade, lngOffset)
						PokeByte(lngBank, lngBankPos, ((lngPixel And $F800) Shr 11) Shl 3)
						PokeByte(lngBank, lngBankPos + 1, ((lngPixel And $7E0) Shr 5) Shl 2)
						PokeByte(lngBank, lngBankPos + 2, (lngPixel And $1F) Shl 3)
						lngOffset = lngOffset + 2
						lngBankPos = lngBankPos + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480

			Case 2
				intY% =0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						lngPixel = PeekShort(bnkScreenFade, lngOffset)
						PokeByte(lngBank, lngBankPos, ((lngPixel And $7C00) Shr 10) Shl 3)
						PokeByte(lngBank, lngBankPos + 1, ((lngPixel And $3E0) Shr 5) Shl 2)
						PokeByte(lngBank, lngBankPos + 2, (lngPixel And $1F) Shl 3)
						lngOffset = lngOffset + 2
						lngBankPos = lngBankPos + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480

			Case 3, 4
				intY% =0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						lngPixel = PeekInt(bnkScreenFade, lngOffset)
						PokeByte(lngBank, lngBankPos, (lngPixel And $FF0000) Shr 16)	; r
						PokeByte(lngBank, lngBankPos + 1, (lngPixel And $FF00) Shr 8)	; g
						PokeByte(lngBank, lngBankPos + 2, lngPixel And $FF)				; b
						lngOffset = lngOffset + bytFormat
						lngBankPos = lngBankPos + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480
				
		End Select

		UnlockBuffer
		
	Next

; Perform the fade using the appropriate "Poke"

	SetBuffer BackBuffer()

	dblAlpha# = 0.0
	Repeat

		dblAlpha = dblAlpha + pdblFadeSpeed
		If dblAlpha > 1 Then dblAlpha = 1

		LockBuffer
		bnkScreenFade% = LockedPixels()
		intPitch% = LockedPitch()
		bytFormat% = LockedFormat()
		lngBankPos1% = 0
		lngBankPos2% = 921600
		
		Select bytFormat
			Case 1
				intY% = 0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						bytCache1% = PeekByte(lngBank, lngBankPos1)
						bytCache2% = PeekByte(lngBank, lngBankPos1 + 1)
						bytCache3% = PeekByte(lngBank, lngBankPos1 + 2)
						PokeInt bnkScreenFade, lngOffset, (((bytCache1 + (PeekByte(lngBank, lngBankPos2) - bytCache1) * dblAlpha) / 8) Shl 11) Or (((bytCache2 + (PeekByte(lngBank, lngBankPos2 + 1) - bytCache2) * dblAlpha) / 4) Shl 5) Or ((bytCache3 + (PeekByte(lngBank, lngBankPos2 + 2) - bytCache3) * dblAlpha) / 8)
						lngOffset = lngOffset + 2
						lngBankPos1 = lngBankPos1 + 3
						lngBankPos2 = lngBankPos2 + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480
			Case 2
				intY% = 0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						bytCache1% = PeekByte(lngBank, lngBankPos1)
						bytCache2% = PeekByte(lngBank, lngBankPos1 + 1)
						bytCache3% = PeekByte(lngBank, lngBankPos1 + 2)
						PokeInt bnkScreenFade, lngOffset, (((bytCache1 + (PeekByte(lngBank, lngBankPos2) - bytCache1) * dblAlpha) / 8) Shl 10) Or (((bytCache2 + (PeekByte(lngBank, lngBankPos2 + 1) - bytCache2) * dblAlpha) / 4) Shl 5) Or ((bytCache3 + (PeekByte(lngBank, lngBankPos2 + 2) - bytCache3) * dblAlpha) / 8)
						lngOffset = lngOffset + 2
						lngBankPos1 = lngBankPos1 + 3
						lngBankPos2 = lngBankPos2 + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480
			Case 3, 4
				intY% = 0
				Repeat
					lngOffset% = (intY * intPitch)
					intX% = 0
					Repeat
						bytCache1% = PeekByte(lngBank, lngBankPos1)
						bytCache2% = PeekByte(lngBank, lngBankPos1 + 1)
						bytCache3% = PeekByte(lngBank, lngBankPos1 + 2)
						PokeInt bnkScreenFade, lngOffset, ((bytCache1 + (PeekByte(lngBank, lngBankPos2) - bytCache1) * dblAlpha) Shl 16) Or ((bytCache2 + (PeekByte(lngBank, lngBankPos2 + 1) - bytCache2) * dblAlpha) Shl 8) Or (bytCache3 + (PeekByte(lngBank, lngBankPos2 + 2) - bytCache3) * dblAlpha)
						lngOffset = lngOffset + bytFormat
						lngBankPos1 = lngBankPos1 + 3
						lngBankPos2 = lngBankPos2 + 3
						intX = intX + 1
					Until intX = 640
					intY = intY + 1
				Until intY = 480
		End Select
		
		UnlockBuffer
		
		VWait
		Flip False

 	Until dblAlpha = 1

	FreeBank lngBank

End Function



Rob Farley(Posted 2004) [#6]
Without looking at your code very closely it looks very complicated for what you're doing.

The basic function for the fade would be (off the top of my head coding no testing or nuffink)




Murilo(Posted 2004) [#7]
That's basically what I'm doing but, as I'm using locked pixels, I need to take the LockedFormat() into account. The code could be condensed (and originally it was), but this was at the expense of some speed. Also, I'm effecively using two banks as I'm cross fading rather than just fading to/from black.

LockPixel poking is faster than WritePixelFast, right?


Rob Farley(Posted 2004) [#8]
LockPixel poking is faster than WritePixelFast, right?
No idea! Test it!


Murilo(Posted 2004) [#9]
It definitely is (from my tests at least), but you threw me for a moment there when you used the older WritePixel in your example :D


MSW(Posted 2004) [#10]
have you tried useing a seperate image stored in system memory as a backbuffer?



erm...

image1 = starting image you want to fade from (store in system memory as a bank or array)
image2 = ending image you want to fade to (again store as a bank or array)

virtualbuffer = createimage with the system memory flag (not stored in video memory)


you then do your fade routine on the virtualbuffer and once done.

drawimage virtualbuffer 0,0
flip

although this might seem strange it's worth a try because it can maximise your use of the system to video bandwidth

writepixelfast is slower then pokeing because the screen cords used in writepixelfast must be converted to a memory address...something already done when pokeing.

But keep in mind that when pokeing pixels or useing writepixelfast you are wasteing your system to video bandwidth potential as , simply stated, both an video memory address as well as an pixel color value are sent across to the video card with each call...not so bad when you have only a few pixels to change...

but when you are dealing with a full screen effect like this it can actualy slow things down because you are effectively sending twice the required information (a 32-bit color value + 32-bit memory address per pixel)

however useing a system image could be faster as it only needs to sent a small amount of info over to the videocard telling it where to send the image, followed by the image itself...rather then a pixel at a time, the bandwidth useage can be maximised because the card knows where to put each pixel (and how many there are) ahead of time...so the whole image can be transfered in one batch rather then each pixel individualy (and the associated memory addressing for that pixel)

at least thats the idea...try it and see if it helps.