Code archives/3D Graphics - Misc/Test if cubemaps are *truly* supported.

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

Download source code

Test if cubemaps are *truly* supported. by Damien Sturdy2008
[Edit] Markcw has now added some more useful code to further extend the test. Also includes demo now.

GFXDriverCaps3D() is a useful function, or, it would be if graphics drivers reported their abilities correctly.

After spending a long while bored and toying with the old B3D (it's been a looong time...) I made this snippet to test if a cubemap is rendered correctly.

It tests if the cubemap texture is pixel perfect with what was rendered on the back buffer. If the backbuffer and the texture do not match, the function returns false.

You might want to alter the texture size to work on a texture the same size as your cubemaps in game to be sure.

Marks code was placed here as apparently my method still did not work on all machines. (it did work on the machines i tested it on... but who knows about other variations of machines!..)

Also, This can be optimised- the test doesn't technically need to render to set up a cubemap and test. Since you only need to call it after initing the graphics window, its hardly going to be slow anyway.
;TestCubeMap function example (fixed by markcw)

;setup scene
Graphics3D 320,240,0,2
SetBuffer BackBuffer()
camera=CreateCamera()
light=CreateLight()
RotateEntity light,90,0,0

;sky sphere
sky=CreateSphere(16)
ScaleEntity sky,500,500,500
FlipMesh sky
EntityFX sky,1
skytex=CreateSkyTexture(256,256)
EntityTexture sky,skytex,0,0

;cubemapped cube
cube=CreateCube()
PositionEntity cube,0,0,5
tex=CreateTexture(32,32,1+128+256)
For face=0 To 5
 If face=0 Then RotateEntity camera,0,90,0 ;left
 If face=1 Then RotateEntity camera,0,0,0 ;front
 If face=2 Then RotateEntity camera,0,-90,0 ;right
 If face=3 Then RotateEntity camera,0,180,0 ;back
 If face=4 Then RotateEntity camera,-90,0,0 ;up
 If face=5 Then RotateEntity camera,90,0,0 ;down
 SetCubeFace tex,face
 RenderWorld
 CopyRect 0,0,32,32,0,0,BackBuffer(),TextureBuffer(tex)
Next
RotateEntity camera,0,0,0
EntityTexture cube,tex

cubemapsupport=TestCubeMap(camera)
caps=GfxDriverCaps3D()

;main loop
While Not KeyDown(1)

 pitch#=0 : yaw#=0
 If KeyDown(208) Then pitch#=-1 
 If KeyDown(200) Then pitch#=1
 If KeyDown(203) Then yaw#=-1
 If KeyDown(205) Then yaw#=1
 TurnEntity cube,pitch#,yaw#,0

 RenderWorld

 Text 0,0,"cubemapsupport="+cubemapsupport
 Text 0,12,"caps="+caps

 Flip
Wend

Function CreateSkyTexture(Xsize,Ysize)
 ;Create a sky sphere texture of given dimensions

 Local hpal,hmap,htex,ix,iy,ip
 hpal=CreatePalette(256,100,160,230,255,255,255) ;cyan gradient
 hmap=CreateHeightMap(hpal,Xsize,Ysize,8,2,1,16,2,0,1,48,0)
 htex=CreateTexture(Xsize,Ysize)
 ;set all heightmap points
 LockBuffer(TextureBuffer(htex))
 For iy=0 To Ysize-1
  For ix=0 To Xsize-1
   ip=PeekByte(hmap,ix+(iy*Xsize)) ;index=x+(y*width)
   WritePixelFast ix,iy,PeekInt(hpal,ip*4),TextureBuffer(htex)
  Next
 Next
 UnlockBuffer(TextureBuffer(htex))
 FreeBank hmap
 FreeBank hpal
 Return htex ;texture handle

End Function

Function CreatePalette(ncol,rmin,gmin,bmin,rmax,gmax,bmax)
 ;Create a palette for a heightmap of given size and color range

 Local hpal,ic,red,green,blue
 hpal=CreateBank(ncol*4)
 For ic=0 To ncol-1
  red=(ic*(rmax-rmin)/ncol)+rmin
  green=(ic*(gmax-gmin)/ncol)+gmin
  blue=(ic*(bmax-bmin)/ncol)+bmin
  PokeInt hpal,ic*4,(red Shl 16)+(green Shl 8)+blue
 Next
 Return hpal ;palette bank handle

End Function

Function CreateHeightMap(hpal,Xdm,Ydm,Xps,Yps,Bps,Blr,Wpr,Mcv,Bcv,Bxs,Bys)
 ;From "lands.bas" by Per Larsson (www.programmersheaven.com)
 ;Xps/Yps/Bps=X/Y/Blur pixel step, Blr=blur (smoothing) amount,
 ;Wpr=water probability (not 0), Mcv=minimum color value,
 ;Bcv=border color value (0 for none), Bxs/Bys=border X/Y size

 Local hmap,hnewmap,ix,iy,ystep,xstep,val,ptx,pty,ib,lf,rt,up,dn
 SeedRnd MilliSecs() ;randomize seed
 hmap=CreateBank(Xdm*Ydm)
 hnewmap=CreateBank(Xdm*Ydm)
 ;make random 2-colors map
 For iy=0 To Ydm-1
  If ystep=0 ;instead of using Step, for variable steps
   For ix=0 To Xdm-1
    If xstep=0
     val=Rand(0,Wpr) ;water probability
     If val=1 : val=BankSize(hpal)-1 : Else : val=1 : EndIf ;set 2-colors
     If Bcv>0 ;draw border around map
      If ix<Bys Or ix>=Xdm-Bys Then val=Bcv
      If iy<Bxs Or iy>=Ydm-Bxs Then val=Bcv
     EndIf
     ;set heightmap points, and boxfill in-between points
     For ptx=0 To Xps-1
      For pty=0 To Yps-1
       PokeByte hmap,(ix+ptx)+((iy+pty)*Xdm),val ;calculate x,y offset
      Next
     Next
    EndIf
    xstep=xstep+1 : If xstep>=Xps Then xstep=0
   Next 
  EndIf
  ystep=ystep+1 : If ystep>=Yps Then ystep=0
 Next
 If Blr=0 Then CopyBank hmap,0,hnewmap,0,Xdm*Ydm ;copy 2-colors map
  ;blur smooth map by pixel steps, average out 2-colors map
  For ib=1 To Blr
   For iy=0 To Ydm-1
    If ystep=0
     For ix=0 To Xdm-1
      If xstep=0
       ;get surrounding points, and wrap overlapping points
       lf=ix-Bps : If lf<0 Then lf=Xdm-Bps
       rt=ix+Bps : If rt>Xdm-Bps Then rt=0
       up=iy-Bps : If up<0 Then up=Ydm-Bps
       dn=iy+Bps : If dn>Ydm-Bps Then dn=0
       ;calculate average of current point, blur
       ;color=(up+lf+rt+down+(pt*2)+lfup+rtup+lfdown+rtdown)/10
       val=PeekByte(hmap,ix+(up*Xdm))+PeekByte(hmap,lf+(iy*Xdm))
       val=val+PeekByte(hmap,rt+(iy*Xdm))+PeekByte(hmap,ix+(dn*Xdm))
       val=val+(PeekByte(hmap,ix+(iy*Xdm))*2)
       val=val+PeekByte(hmap,lf+(up*Xdm))+PeekByte(hmap,rt+(up*Xdm))
       val=val+PeekByte(hmap,lf+(dn*Xdm))+PeekByte(hmap,rt+(dn*Xdm))
       val=val/10
       If val<Mcv Then val=Mcv ;set minimum color
       If ib>1 Then PokeByte hmap,ix+(iy*Xdm),val ;set smoothed average
       ;Set actual heightmap points, in pixel steps
       For ptx=0 To Bps-1
        For pty=0 To Bps-1
         PokeByte hnewmap,(ix+ptx)+((iy+pty)*Xdm),val
        Next
       Next
      EndIf
      xstep=xstep+1 : If xstep>=Bps Then xstep=0
     Next
    EndIf
    ystep=ystep+1 : If ystep>=Bps Then ystep=0
   Next
  Next
 FreeBank hmap
 Return hnewmap ;heightmap bank handle

End Function

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;ACTUAL FUNCTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Function TestCubeMap(maincamera)
 ;This function tests if a card truely does support cubemapping.
 ;Unfortunately some cards report that they do when they in fact do not.
 ;This results in garbled output. To test properly, we render some
 ;cubemaps and check if the texture matches what was rendered to the BackBuffer.
 ;Function by Damien Sturdy AKA Cygnus, Modified by MarkCW for accuracy.
 ;returns true or false. true if the test succeeded.

 If GfxDriverCaps3D()<>110 Then Return 0
 Local camera,tex,texsize=32
 Local clr[5],badpixels,tolerance=10
 Local ok=1,x,y,face,p1,p2
 camera=CreateCamera()
 CameraProjMode maincamera,0
 CameraViewport camera,0,0,texsize,texsize
 tex=CreateTexture(texsize,texsize,1+128+256) 
 For face=0 To 5
  If face=0 Then RotateEntity camera,0,90,0 ;left
  If face=1 Then RotateEntity camera,0,0,0 ;front
  If face=2 Then RotateEntity camera,0,-90,0 ;right
  If face=3 Then RotateEntity camera,0,180,0 ;back
  If face=4 Then RotateEntity camera,-90,0,0 ;up
  If face=5 Then RotateEntity camera,90,0,0 ;down
  SetCubeFace tex,face
  RenderWorld
  CopyRect 0,0,texsize,texsize,0,0,BackBuffer(),TextureBuffer(tex)
  For x=1 To texsize-2
   For y=1 To texsize-2
    p1=ReadPixel(x,y,BackBuffer()) And $FFFFFF
    p2=ReadPixel(x,y,TextureBuffer(tex)) And $FFFFFF
    clr[0]=((p1 And $FF0000)/$FF00)/16
    clr[3]=((p2 And $FF0000)/$FF00)/16
    clr[1]=((p1 And $00FF00)/$FF)/16
    clr[4]=((p2 And $00FF00)/$FF)/16
    clr[2]=(p1 And $0000FF)/16
    clr[5]=(p2 And $0000FF)/16
    If clr[0]<>clr[3] And clr[1]<>clr[4] And clr[2]<>clr[5]
     badpixels=badpixels+1
    EndIf
   Next
  Next
 ;Print " " : Delay(500)
 Next
 If badpixels<(((6*texsize*texsize)/100)*tolerance) Then ok=1
 CameraProjMode maincamera,1
 FreeEntity camera
 FreeTexture tex
 Return ok

End Function

Comments

Snarkbait2008
http://dictionary.reference.com/search?q=truly


Damien Sturdy2008
Was that really needed?... (I'm sorry for not bothering to proof read my text. I could have, you know, not bothered to share the code too.)

Fixed. (Why can you edit the titles here but not in the forums?)


Snarkbait2008
No offense meant. That's just a common misspelling that really bugs me. ;)


Damien Sturdy2008
Okiedoke :-) not sure why I was even so uptight about it hehe.


markcw2008
I was curious to see if this worked so I set up a small scene to apply the cubemap test to and it didn't work!

This is a good idea if some cards don't work like you say. It's badly realized however.

First you check the edges of the texture which is not going to be pixel perfect because of bleeding. Second your pixel perfect approach is no use to anyone really, so I changed it to check red, green and blue are similar in value. Also instead of rejecting cubemap support if some pixels are wrong, I added a bad pixel count and if it's above a certain tolerance then it rejects cubemap support. Also, you weren't rotating the camera so your cubemap was rendering the same view each time.




Damien Sturdy2008
@Mark, ok, thanks for testing this- It worked here but I only had access to three machines. The function worked accurately on these three.


Also, you weren't rotating the camera so your cubemap was rendering the same view each time.


You're supposed to call it after Graphics3D, in place of your DriverCaps test. Since when you call it after graphics3D, you wont have a scene to render, no rotation is necesary. in fact, you could skip calling render completely and just cls to a certain colour. The test it to simply see if the texture comes out correct. Therefore there is no need to rotate the camera- this was missed on purpose.
Saying that, I suppose there is a possibility that a non-compliant card filles a cubemap with black, therefore making a false pass? can't say i've seen that before though.

ALthough, thanks for updating. I hoped people would do this! :-)

You do have a better method here, and your bad pixel count is a great idea- if a few pixels are missing then it won't normally affect the whole scene so doesnt necesarily mean they are useless.

I hope you don't mind me updating the original post with your code so that people get the most up to date version?


markcw2008
Well, rendering a plain color isn't really a proper test, better to render an actual scene.

The bad pixel method wasn't actually necessary to get it working. Once I avoided checking the pixels on the very edge I could do a pixel perfect check, but I think it's better to be a little generous since some cards may be a little poor.

Of course not.


markcw2008
Cygnus, I just noticed a little error, actually this explains something I couldn't understand. Your code here was wrong:

CopyRect 0,0,texsize-1,texsize-1,0,0,BackBuffer(),TextureBuffer(tex)


You were copying texsize-1 for width and height, so when you checked those pixels they were always wrong. This is why it didn't work. So I thought it was bleeding but it was just this error. Still, there's no harm in avoiding the edges anyway.

Code updated.


Damien Sturdy2008
Oof! In which case I actually put old code up! I initially did that because as a Max user i'm used to things being 0-31. (a 32x32 texture would be 0-31,0-31 here.) but the code I have here actually doesn't have the -1,-1.

I updated the initial post.

I was going to comment on your texture comment as I wasn't sure how a texture buffer could "wrap" or blur. I should check my code more! ;-)


markcw2008
Hi, well if you hadn't made that little mistake I wouldn't have improved your code. I think it's essential to actually render a scene properly, if you just render a single color maybe a card could do that but not do a proper scene.

Yeah, I couldn't understand why it wouldn't work so I just assumed it was because of bleeding but you only get that when you render to texture.

Edit: also, you missed my other edit here:
CopyRect 0,0,32-1,32-1,0,0,BackBuffer(),TextureBuffer(tex)



Damien Sturdy2008
updated. Lol, I recognise that heightmap code- used it in a few Qbasic projects back in the day! :-)


think it's essential to actually render a scene properly, if you just render a single color maybe a card could do that but not do a proper scene.


I was pondering on this too, but the machines i tested it on when failed, just put garbage on the texture, rather than managed the single colour. still, better to be sure eh!

Mostly its the Intel chipsets that fail. certainly this 945 chipset with latest drivers fails. My old Nvidia fails too but that one is clearly a driver bug. The function is good because it detects both lack of hardware support and if a driver is buggy.

Thanks for your tweaks BTW, it's even more useful now.


Code Archives Forum