PDA

View Full Version : Fire effect from Doom port for original Playstation :D



Petr Schreiber
28-05-2019, 11:09
Fabien Sanglard posted quite cool analysis of fire effect, used for PSX port of the original Doom.

It is a fascinating read (http://fabiensanglard.net/doom_fire_psx/index.html), highly recommended.

I ported the code to thinBasic, with some slight modifications. It could be optimized way better, but the effect pretty pretty:


' Code adapted from http://fabiensanglard.net/doom_fire_psx/index.html and slightly modified


uses "ui"


type DoomPsxFire


fire_width as int32
fire_height as int32


palette(37) as int32 ' Holds palette of fire colors
fire() as Int32 ' Holds indices referencing the paletter


function _Create(fire_width as int32, fire_height as int32)
me.fire_width = fire_width
me.fire_height = fire_height

me.palette(1) = bgr(0x07,0x07,0x07), bgr(0x1F,0x07,0x07), bgr(0x2F,0x0F,0x07), bgr(0x47,0x0F,0x07), bgr(0x57,0x17,0x07), bgr(0x67,0x1F,0x07),
bgr(0x77,0x1F,0x07), bgr(0x8F,0x27,0x07), bgr(0x9F,0x2F,0x07), bgr(0xAF,0x3F,0x07), bgr(0xBF,0x47,0x07), bgr(0xC7,0x47,0x07),
bgr(0xDF,0x4F,0x07), bgr(0xDF,0x57,0x07), bgr(0xDF,0x57,0x07), bgr(0xD7,0x5F,0x07), bgr(0xD7,0x5F,0x07), bgr(0xD7,0x67,0x0F),
bgr(0xCF,0x6F,0x0F), bgr(0xCF,0x77,0x0F), bgr(0xCF,0x7F,0x0F), bgr(0xCF,0x87,0x17), bgr(0xC7,0x87,0x17), bgr(0xC7,0x8F,0x17),
bgr(0xC7,0x97,0x1F), bgr(0xBF,0x9F,0x1F), bgr(0xBF,0x9F,0x1F), bgr(0xBF,0xA7,0x27), bgr(0xBF,0xA7,0x27), bgr(0xBF,0xAF,0x2F),
bgr(0xB7,0xAF,0x2F), bgr(0xB7,0xB7,0x2F), bgr(0xB7,0xB7,0x37), bgr(0xCF,0xCF,0x6F), bgr(0xDF,0xDF,0x9F), bgr(0xEF,0xEF,0xC7), bgr(0xFF,0xFF,0xFF)

redim me.fire(me.fire_width*me.fire_height)

int32 i

' Initialize screen
for i = 1 to me.fire_width
me.fire(i) = 37 ' Initial color, just for one row
next

for i = me.fire_width+1 to me.fire_width*me.fire_height
me.fire(i) = 1 ' Background color
next
end function

function DoFire()
int32 x, y
for x = 1 to me.fire_width
for y = 1 to me.fire_height-1
me.SpreadFire((y-1) * me.fire_width + x)
Next
Next
end function

' --

function SpreadFire(src as int32)
int32 rand = rnd(0, 3) and 3
int32 target_pixel = minMax(src + rand - 2 + me.FIRE_WIDTH, 1, ubound(me.fire))
me.fire(target_pixel) = minmax(me.fire(src) - (rand and 1), 1, 37)
end function

function GetBitmap() as String
dim trueColors(me.fire_width * me.fire_height) as Int32

int32 fire_index, true_color_index
int32 fire_color_index

' We need to convert palette colors to real colors
for fire_index = ubound(me.fire) to 1 step -1
fire_color_index = me.fire(fire_index)


incr true_color_index
trueColors(true_color_index) = me.palette(fire_color_index)
next


' Once the conversion is done, we lick part of the memory to get bitmap
return peek$(VarPtr(trueColors(1)), sizeof(int32) * countof(trueColors))
end function


end type


%FIRE_WIDTH = 128
%FIRE_HEIGHT = 96


function TBMain()


uint32 hCanvas = Canvas_Window("Doom PSX Fire", 100, 100, %FIRE_WIDTH, %FIRE_HEIGHT)
Canvas_attach(hCanvas, 0, FALSE)


dim doom as DoomPsxFire(%FIRE_WIDTH, %FIRE_HEIGHT)


while IsWindow(hCanvas)


doom.DoFire()

Canvas_BitmapSet(doom.GetBitmap(), %FIRE_WIDTH, %FIRE_HEIGHT)
Canvas_Redraw

wend

Canvas_Window End

end Function





Petr

ErosOlmi
28-05-2019, 13:59
Thanks a lot Petr!
A great example to push thinBasic at its limits.
Calling thousand of times script functions is not the best for thinBasic :oops:

Here I've got a little speed improve merging 2 functions into one in such a way to avoid function calling thousand of times per second. Code i less elegant but is faster :)
I will use this example to see if I can improve Core engine to parse UDT elements


' Code adapted from http://fabiensanglard.net/doom_fire_psx/index.html and slightly modified

uses "ui"

type DoomPsxFire

fire_width as int32
fire_height as int32


palette(37) as int32 ' Holds palette of fire colors
fire() as Int32 ' Holds indices referencing the paletter


function _Create(fire_width as int32, fire_height as int32)
me.fire_width = fire_width
me.fire_height = fire_height

me.palette(1) = bgr(0x07,0x07,0x07), bgr(0x1F,0x07,0x07), bgr(0x2F,0x0F,0x07), bgr(0x47,0x0F,0x07), bgr(0x57,0x17,0x07), bgr(0x67,0x1F,0x07),
bgr(0x77,0x1F,0x07), bgr(0x8F,0x27,0x07), bgr(0x9F,0x2F,0x07), bgr(0xAF,0x3F,0x07), bgr(0xBF,0x47,0x07), bgr(0xC7,0x47,0x07),
bgr(0xDF,0x4F,0x07), bgr(0xDF,0x57,0x07), bgr(0xDF,0x57,0x07), bgr(0xD7,0x5F,0x07), bgr(0xD7,0x5F,0x07), bgr(0xD7,0x67,0x0F),
bgr(0xCF,0x6F,0x0F), bgr(0xCF,0x77,0x0F), bgr(0xCF,0x7F,0x0F), bgr(0xCF,0x87,0x17), bgr(0xC7,0x87,0x17), bgr(0xC7,0x8F,0x17),
bgr(0xC7,0x97,0x1F), bgr(0xBF,0x9F,0x1F), bgr(0xBF,0x9F,0x1F), bgr(0xBF,0xA7,0x27), bgr(0xBF,0xA7,0x27), bgr(0xBF,0xAF,0x2F),
bgr(0xB7,0xAF,0x2F), bgr(0xB7,0xB7,0x2F), bgr(0xB7,0xB7,0x37), bgr(0xCF,0xCF,0x6F), bgr(0xDF,0xDF,0x9F), bgr(0xEF,0xEF,0xC7),
bgr(0xFF,0xFF,0xFF)

redim me.fire(me.fire_width * me.fire_height)

int32 i

' Initialize screen
for i = 1 to me.fire_width
me.fire(i) = 37 ' Initial color, just for one row
next

for i = me.fire_width + 1 to me.fire_width * me.fire_height
me.fire(i) = 1 ' Background color
next
end function

function DoFire()
static x, y as Int32
static rand as int32
static target_pixel as int32
static src as int32

for x = 1 to me.fire_width
for y = 1 to me.fire_height-1
'me.SpreadFire((y-1) * me.fire_width + x)


src = (y-1) * me.fire_width + x
rand = rnd(0, 3) and 3
target_pixel = minMax(src + rand - 2 + me.FIRE_WIDTH, 1, ubound(me.fire))

me.fire(target_pixel) = minmax(me.fire(src) - (rand and 1), 1, 37)

Next
Next
end function

' ' --
' function SpreadFire(src as int32) as long
' static rand as int32
' static target_pixel as int32
'
' rand = rnd(0, 3) and 3
' target_pixel = minMax(src + rand - 2 + me.FIRE_WIDTH, 1, ubound(me.fire))
'
' me.fire(target_pixel) = minmax(me.fire(src) - (rand and 1), 1, 37)
'
' end function

function GetBitmap() as String
static trueColors(me.fire_width * me.fire_height) as Int32

Int32 fire_index, true_color_index
int32 fire_color_index

' We need to convert palette colors to real colors
for fire_index = ubound(me.fire) to 1 step -1
fire_color_index = me.fire(fire_index)

incr true_color_index
trueColors(true_color_index) = me.palette(fire_color_index)
next

'---Once the conversion is done, we lick part of the memory to get bitmap
function = peek$(VarPtr(trueColors(1)), sizeof(int32) * countof(trueColors))

end function


end type


%FIRE_WIDTH = 160
%FIRE_HEIGHT = 120


function TBMain()


uint32 hCanvas = Canvas_Window("Doom PSX Fire", 100, 100, %FIRE_WIDTH, %FIRE_HEIGHT)
Canvas_attach(hCanvas, 0, FALSE)


dim doom as DoomPsxFire(%FIRE_WIDTH, %FIRE_HEIGHT)


while IsWindow(hCanvas)


doom.DoFire()

Canvas_BitmapSet(doom.GetBitmap(), %FIRE_WIDTH, %FIRE_HEIGHT)
Canvas_Redraw

wend

Canvas_Window End

end Function

DirectuX
28-05-2019, 18:25
Calling thousand of times script functions is not the best for thinBasic :oops:


would Memoization be relevant here ?

Petr Schreiber
28-05-2019, 20:28
Hi DirectuX,

very cool idea - as the functions modify global state of the object (indicating a bit of code smell), I presume memoization would be more difficult to setup here.


Petr

Petr Schreiber
28-05-2019, 21:10
Further speedup achieved by reducing two nested for/next loops to a single one. Gave me 10%+ extra performance:


function DoFire()
static rand as int32
static target_pixel as int32
static src as int32

static top as int32 = (me.fire_width-1) * me.fire_height
for src = 1 to top
rand = rnd(0, 3) and 3
target_pixel = minMax(src + rand - 2 + me.FIRE_WIDTH, 1, ubound(me.fire))


me.fire(target_pixel) = minmax(me.fire(src) - (rand and 1), 1, 37)
Next
end function


Further optimization could lead to avoid calling rnd every time, and using some pre generated "randoms", already with "-2 + me.fire_width" included.


Petr

DirectuX
29-05-2019, 15:40
Further speedup achieved by reducing two nested for/next loops to a single one. Gave me 10%+ extra performance:


target_pixel = minMax(src + rand - 2 + me.FIRE_WIDTH, 1, ubound(me.fire))



ubound(me.fire) can also be stored in a variable before iterating

and.. what about Oxygen ?

Edit: Seems I can't put together thinbasic's SharedArray.tbasic oxygen sample script and UDTs

Charles Pegge
30-05-2019, 07:39
me.fire is a dynamic array. To access it as a parameter , I found that oxygen had to use 2 additional levels of indirection.

ErosOlmi
30-05-2019, 10:24
ubound(me.fire) can also be stored in a variable before iterating

and.. what about Oxygen ?

Edit: Seems I can't put together thinbasic's SharedArray.tbasic oxygen sample script and UDTs

I'm using this Petr example to make further tests.
I found some problems in thinBasic thanks to this example.

For example

VARPTR(me.fire(1))
is wrong :oops: sorry about that.

Instead of returning a pointer to first data element of the dynamic array it returns the pointer of location inside Me where Fire pointer array structure is located (that is a pointer to the array data structure)
So I'm making some fix and will release soon.

Charles Pegge
30-05-2019, 13:30
Here is my oxygen-supported code keeping as close to Petr's original code as possible

The canvas is 4 times larger, but the cpu time is 0% :)

Petr Schreiber
30-05-2019, 22:06
Hi Charles,

nice to see you again :)

I get "unidentified instruction: uses" error for your example.


Petr

Charles Pegge
30-05-2019, 22:26
Hi Petr, :)

You have an older oxygen.

Try:

"include `doom_psx_fire.inc` "

ErosOlmi
31-05-2019, 06:01
Ciao Charles,

I use Github at https://github.com/Charles-Pegge/OxygenBasic as a source for updating Oxygen in.
Is it correct?

Eros.

Charles Pegge
31-05-2019, 06:36
Yes, Eros.

I wil be releasing another (overdue) GitHub update within the next week.

I would also like to include this example, but to avoid the dynamic array referencing problem, I would make the fire array static for now.

ErosOlmi
31-05-2019, 06:38
OK thanks.

DirectuX
04-06-2019, 23:36
When moving mouse over the activated window, there is a small fps boost.
When holding the window left-clicked, there is a huge fps boost and processor usage jumps from 8 % to 18 %.
I'm curious to understand this.

video 9958

Charles Pegge
05-06-2019, 13:30
I think the canvas refresh is in sync with the 60Hz screen refresh, but also activates whenever certain windows messages are being processed.

DirectuX
05-06-2019, 17:50
that makes sense to me, thanks Charles