Unofficial glmax2d

BlitzMax Forums/BlitzMax Programming/Unofficial glmax2d

slenkar(Posted 2009) [#1]
Here is a basic glmax2d.
http://www.blitzbasic.com/codearcs/codearcs.php?code=2612

Feel free to make additions - send them to me and i will add them.

Or you could request features to be added.


ImaginaryHuman(Posted 2009) [#2]
Given what you said in your code archive comments, it looks like the only thing you added was a) ability to define an irregular quad image, and b) ability to add a gradient to drawtext? Not to mock it or anything but with nothing much else `new` why would people want to use it?


slenkar(Posted 2009) [#3]
there are features that people want in max2d that are not being implemented,

With this simple version of glmax2d people can request features (in this thread) and they can be put in as long as OpenGL supports those features.

Not only features but optimizations.


Armitage 1982(Posted 2009) [#4]
A few features I'd love to see in GLMax2d :

- Batch Rendering, I didn't read anything on this yet.

- Native functions to draw animated and textured polygons (concave and convex).

- SetViewport() replacement since I read everywhere it causes problems on old GFX cards.

- More color blending mode (like the one you can use in the Gimp for example).

- Fixed rate logic and basic Tweening modules.

- Built-in bezier curve and advanced rendering commands (like in Cairo Brucey module).

- Easy shaders support but I probably dream on this one ;)

OTH


ImaginaryHuman(Posted 2009) [#5]
Ahh so you want some ideas? Ok.

- Unfilled circle/box drawing
- Use of vertex arrays to render lots of images/quads/irregular quads, also to do lines and single pixels - ie so that you can draw lots of particles efficiently.
- More blend modes - GL does let you do more stuff even in 1.1
- Stencil buffer commands
- Textured polygons
- Faster circle/ellipse than one that call trig functions (see my code archives for integer solutions)
- Grab to texture (glCopyTexSubImage2D()) and use it for drawimage
- Store multiple sprites on a single image and draw individual sprites using proper texture coordinates - coupled with your individually positionable vertices
- Define vertex color per vertex - lets you create gouraud shading easily
- A basic tile engine?


slenkar(Posted 2009) [#6]
batch rendering -
what kind of opengl feature do you mean?

In OpenGL you have display lists to draw polygons faster
theres vertex buffer objects
theres also putting several textures on the same texture

which do you mean?

-textured polygons
I think Indiepath has a textured polygon module
which I can implement,
-animated polygons
you can use the setimagevertices commands to animate the vertices

-setviewport replacement
is there an opengl command better than glviewport?
how about the glscissor commands?

-color blending modes
will look into it

-fixed rate logic
hasnt got anything to do with opengl really,

-bezier curve

I dont know if there are any examples of this in the code archives, I shall take a look later

-shaders

I dont own any graphics cards that can do shaders sorry


If anyone else has any ideas how to implement these functions please chime in


slenkar(Posted 2009) [#7]
- Unfilled circle/box drawing

yes, should be easy, I had the code to do this

- Use of vertex arrays to render lots of images/quads/irregular quads, also to do lines and single pixels - ie so that you can draw lots of particles efficiently.

yes should be okay
which opengl command does this?
VBO's?
display list's?

- More blend modes - GL does let you do more stuff even in 1.1


yep should be okay

- Stencil buffer commands

might need some help here, I had a look at some stencil buffer documentation = couldnt make head nor tail of it.

- Textured polygons

yep, Indiepath has already done it, integrating it shouldnt be a problem

- Faster circle/ellipse than one that call trig functions (see my code archives for integer solutions)

ok, if you have already done the code yourself, should be quick to implement

- Grab to texture (glCopyTexSubImage2D()) and use it for drawimage

interesting,
what are the advantages out of curiosity?

- Store multiple sprites on a single image and draw individual sprites using proper texture coordinates - coupled with your individually positionable vertices

yes, should be easy


- Define vertex color per vertex - lets you create gouraud shading easily

yes should be easy

- A basic tile engine?

hmm possibly, seems like it could be out of scope of this project, I think most people like to use their own?


TaskMaster(Posted 2009) [#8]
From what I understand, SetViewPort() works in OpenGL. It is the DirectX version that does not work.


beanage(Posted 2009) [#9]
This "basic tile engine" thing sounds pretty sarcastic imo.

Nice there's finally an easy public max2d module option to build upon, thank you.


slenkar(Posted 2009) [#10]
TaskMaster - well theres definetly no plans for a directx version so thats not a problem :)

beanage - you're welcome,
please share if you make some cool additions


Jesse(Posted 2009) [#11]
missed the payback. Ha...Ha...Ha...


slenkar(Posted 2009) [#12]
lol

how about I translate my fist to your face?









j/k


ImaginaryHuman(Posted 2009) [#13]
By basic tile engine I just mean a) have some simple data structure into which you can assign sub-image p to tile at tile coords x,y, then call like `draw map` to draw it, or something simple like that. Even a very simple tilemap system would help beginners especially to make quick progress.

The stencil buffer can be a bit complicated and the way it's set up in OpenGL is sort of not very straightforward.

As you draw something to the screen, you can have the stencil buffer test for the presence of a `set bit` in the stencil, and then decide whether to allow that particular pixel to be drawn. At the same time as testing existing stencil bits, you can write to the stencil those pixels which gets past the stencil test. So it's kind of like you do a read from the stencil, test with it, then optionally also write to the stencil. But setting it up is not very easy to grasp - reference values and stencil functions and all that.

To help you out, I am providing below my current stencil-buffer code. You can use it freely for whatever you like. Blitz on the Amiga had stencils and they could be quite useful. There is a lot you can use them for. You can draw whatever shape you like as a stencil, made up of as many drawing commands as you like, and then that area can be protected from or stencil-in future drawing operations, letting you easily draw behind stuff, etc.

After a LOT of consideration of the functionality provided by the stencil buffer I came up with these functions which I hope/think captures everything you'd want to do.

I refer to the ability to write to the stencil as `stencil recording` which you set up with GStencilRecord(). The `threshold` and the write and read `masks` work together to let you select/work with/write to/read from any set of bits in the stencil buffer. On most systems the stencil buffer is usually an 8-bit byte buffer (one byte per pixel), but for ease you can just write $FF to set and test a pixel). I refer to the ability to `test the stencil` and fail/pass pixels, as `stencilling` which can be enabled or disabled with GStencilling().

You also might want the GOutput() function, which lets you switch off writes to the color buffer - sometimes you want to draw stuff to the stencil buffer without wasting time drawing the actual pixels in color. Even without drawing in color, pixels can still be calculated and recorded/tested in the stencil.

Also I use something I set up called `multi begin` and `multi end` which basically is just an attempt to keep track of whether I would have used a glBegin() glEnd() and for which type of geometry. If you then do a graphics op like draw more quads and you already just drew some quads, you wouldn't need to start glBegin() again so that's just one way to manage it. So you can see references to that in the code - you can take it out or recode it. It helps speed things up a bit in some immediate mode operations.

Note that I pass normal OpenGL constants for some parameters, you may want to make your own more friendly/easy-to-remember constants. Note also I included GClearStencil() which you'll need to call at least once before you use the stencil buffer, and optionally each frame. You can, however, set the stencil to clear to 0 the bits you have tested, which is like clearing as you go and is more efficient.

Note also in GStencilRecord it refers to 3 operations, which relate to the results of the depth test (if enabled - which it normally is not). See GL documentation for how to use, but generally the third of the three is the most useful. GL_REPLACE just means it will replace the contents of the stencil buffer if the zbuffer test was okay (or switched off). You can use others like GL_ZERO to clear it after testing. You can also define how the testing is performed, like if a value is greater than, equal, less than or equal, notequal, etc versus the threshold value, combined with masking. It does get complicated.

'GL stencil testing modes
Global GStencil:Int=False						'Whether to stencil/mask drawn pixels using the stencil buffer content
Global GStenMode:Int=GL_ALWAYS				'What GL mode To test the stencil with To determine whether To draw pixels
Global GStenThreshold:Int=0					'The stencil test reference value
Global GStenReadMask:Int=$FFFFFFFF					'A mask AND'ed with the threshold value and stencil-buffer-pixel when doing a test, to test against specific bits
Global GStenWriteMask:Int=$FFFFFFFF				'A mask AND'ed with the stencil pixel being written to write only to specific bits
Global GStenRecord:Int=False					'Whether to record new stencil content using incoming fragments
Global GStenRecordFailMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the stencil test fails
Global GStenRecordZFailMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the Z-buffer test fails but the stencil test passes
Global GStenRecordZPassMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the stencil test passes and the depth test fails (or is not used)

Function GStencilRecord(DoRecording:Int=False,FailMode:Int=GL_REPLACE,ZFailMode:Int=GL_REPLACE,ZPassMode:Int=GL_REPLACE)
	'Set whether to record new drawn geometry into the stencil, and in what mode based on the result of stencil and depth tests
	'Allowed: GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, and GL_INVERT
	
	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Switch stencil recording on or off and set its mode
	If DoRecording=True
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)		'Define the test function
		glStencilOp(FailMode,ZFailMode,ZPassMode)	'Define the recording operation
		glEnable(GL_STENCIL_TEST)			'Switch on testing so that the record works, even if testing is GL_ALWAYS
		GStenRecordFailMode=FailMode			'Store mode
		GStenRecordZFailMode=ZFailMode		'Store mode
		GStenRecordZPassMode=ZPassMode		'Store mode
		GStenRecord=True					'Recording
		Return
	Else
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)		'Define the test function
		glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP)	'Define the recording operation
		If GStenMode=GL_ALWAYS Then glDisable(GL_STENCIL_TEST)		'Switch off stencilling if not required
		GStenRecordFailMode=GL_KEEP			'Store mode (if you switch stencil record off, recording mode resets)
		GStenRecordZFailMode=GL_KEEP
		GStenRecordZPassMode=GL_KEEP
		GStenRecord=False					'No recording
		Return
	EndIf
End Function

Function GStencilling(DoStencilling:Int=False,Mode:Int=GL_ALWAYS)
	'Change whether to use the stencil to restrict drawing, and in what mode

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Switch stencilling on or off and set the mode
	If DoStencilling=True
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(Mode,GStenThreshold,GStenReadMask)		'Set it
		glStencilOp(GStenRecordFailMode,GStenRecordZFailMode,GStenRecordZPassMode)		'Set the stencil op
		glEnable(GL_STENCIL_TEST)				'Enable testing
		GStenMode=Mode						'Store the mode
		GStencil=True						'Store state
		Return
	Else
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(Mode,GStenThreshold,GStenReadMask)		'Set it
		glStencilOp(GStenRecordFailMode,GStenRecordZFailMode,GStenRecordZPassMode)		'Set the stencil op
		If GStenRecordFailMode=GL_KEEP And GStenRecordZFailMode=GL_KEEP And GStenRecordZPassMode=GL_KEEP Then glDisable(GL_STENCIL_TEST)		'Disable testing if not required
		GStenMode=Mode					'Store the mode
		GStencil=False						'Store state
		Return
	EndIf
End Function

Function GStencilThreshold(Threshold:Int=0)
	'Set the stencil compare value - the value from the stencil at which pixels are allowed to be drawn

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the threshold
	glStencilFunc(GStenMode,Threshold,GStenReadMask)	'Set it
	GStenThreshold=Threshold					'Store the threshold
End Function

Function GStencilWriteMask(Mask:Int=$FFFFFFFF)
	'Change the bit-mask used in stencil testing to confine which bits are writeable

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the mask
	glStencilMask(Mask)						'Set it
	GStenWriteMask=Mask						'Store the mask
End Function

Function GStencilReadMask(Mask:Int=$FFFFFFFF)
	'Change the bit-mask used in stencil testing to confine which bits are readable during testing

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the mask
	GStenReadMask=Mask						'Store the mask
	glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)	'Set it
End Function

Function GClearStencil(Value:Int=0)
	'Clear the stencil buffer to a given value
	
	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Clear it
	glClearStencil(Value)						'Set the clear value
	glClear(GL_STENCIL_BUFFER_BIT)			'Clear the stencil buffer
End Function

Function GOutput(AllowRed:Int=GL_FALSE,AllowGreen:Int=GL_FALSE,AllowBlue:Int=GL_FALSE,AllowAlpha:Int=GL_FALSE)
	'Define which of the color-buffers channels are enabled for writing output
	'Allowed GL_TRUE or GL_FALSE as parameters

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Define output channels
	glColorMask(AllowRed,AllowGreen,AllowBlue,AllowAlpha)		'Set it
End Function

Function GMultiBegin(Shape:Int=GL_QUADS)
	'Handle multiple calls to glBegin of the same or differnt geometry type
	'If we're already inside a glBegin of a particular type, another glBegin
	'will not be issued if Shape is the same. When Shape changes to a different
	'type of geometry, we glEnd() the current type and start a new glBegin type.
	'Be aware that only certain OpenGL commands are allowed inside a glBegin-glEnd
	'pair, so make sure to GMultiEnd() before issuing those commands.
	'Note that using GMultiBegin without GMultiEnd, geometry may not be drawn
	'until a GMultiEnd is called (like a flush command). You can make multiple calls to
	'GMultiBegin() without calling GMultiEnd() until later when you are sure you want to flush

	'Begin?
	If GBegun=-1
		'Begin a new shape
		glBegin(Shape)						'Begin a new shape
		GBegun=Shape						'We have begun this shape, need a glEnd() to follow
		Return
	Else
		'Begun already, begin a different shape?
		If GBegun<>Shape
			glEnd()
			glBegin(Shape)					'Begin a new, different shape
			GBegun=Shape					'We are beginning a new shape, need a glEnd() to follow
		EndIf
		Return
	EndIf
End Function

Function GMultiEnd()
	'To finish a block started with MultiBegin()
	'This is a substitute for glEnd() which takes into account the state of GMultiBegin
	'GMultiBegin will automatically insert a glEnd() if changing geometry type,
	'but if you need to end a geometry definition and start using commands which are
	'not allowed inside a glBegin/glEnd block, you need to issue a GMultiEnd.
	'Note that if geometry is being defined open-endedly by not calling GMultiEnd
	'very often, geometry may not be drawn until GMultiEnd is called to complete it.
	'but you can make multiple calls to GMultiBegin() and only call GmultiEnd() when
	'you want to use commands which don't work in a glBegin-glEnd block. GMultiBegin will
	'automatically call glEnd() if changing geometry type.
	
	'End?
	If GBegun<>-1
		glEnd()								'End the geometry
		GBegun=-1							'Ended all glBegins
	EndIf
End Function


I recommend looking at vertex arrays initially for your further batching of rendering calls. It can be 2 or more times faster to render. Display lists are groovy but are becoming a bit depeciated. Often a vertex array is just as fast or nearly so. A vertex buffer object is like storing a vertex array in video ram rather than passing it across the bus, which if the array content doesn't change much could be a lot faster - like maybe for a color array and texture coord array which may not change as much as vertices.

You should also try to implement having multiple images on a single image and drawing those sub images individually. You should then try to make as few texture selections as possible. Ie choose a texture, render as many sprites from it as you can, then switch to another texture. Texture swaps slow thing down. If you can put particles on a single texture and render an entire particle engine effect it'll be much quicker that way.

glCopyTexSubImage() is the quickest and most backwards compatible way to copy a rectangle from the backbuffer into a texture. You don't have to copy the whole screen. It's fast enough that on most systems you can draw a screen full of graphics, grab it, then re-draw it in some interesting way - perhaps warp it with a mesh, or use it to preserve graphics between frames, or to do feedback effects, etc.


Armitage 1982(Posted 2009) [#14]
From what I understand, SetViewPort() works in OpenGL. It is the DirectX version that does not work.


Humm good news then :)

-fixed rate logic
hasnt got anything to do with opengl really,

Well sorry... At first I though it was about a new Max2d module and since the Graphics command and Flip do have a software vsync.

About the rest, ImaginaryHuman explained everything in a much better ways ;)


ImaginaryHuman(Posted 2009) [#15]
SetViewport works because if the viewport is smaller than the whole screen it a) sets a viewport and b) switches on the scissor test. `Scissoring` basically sets up four clipping planes, defining a rectangle, and then when scissor testing is switched on it tests each pixel to see if it is inside the rectangle. If so, it gets drawn. So you should be good to go.

I forgot to mention about the stencil buffer, that if you want to do *anything* with stencils you have to make sure to include STENCIL_BUFFER as one of the display flags, either in your graphics driver default flags or pass it to Graphics/GLGraphics. That will create the stencil buffer. You can check if the buffer exists by asking OpenGL with glGetIntegerv ... something about `stencil bits`. If >0 you're good to go.


slenkar(Posted 2009) [#16]
heres my grabimage function:
Function GrabImage:TImage(x,y,width,height)
Local i:TImage=CreateImage(width,height)
glBindTexture GL_TEXTURE_2D,i.num
glCopyTexSubImage2D GL_TEXTURE_2D,0,0,0,x,y,width,height
Return i
End Function

Strict
Framework keef.gldraw

initgl(800,600,0)

Local i:TImage
Local grabbed=False
Local j:TImage=LoadImage("turn arrow.png")

While Not KeyDown(key_escape)

Cls
SetScale(25,25)
DrawImage(j,0,0)

i=GrabImage(170,50,50,50)

If i<>Null
SetScale(1,1)
DrawImage(i,170,100)
EndIf

Flip




Wend


it just draws a white square though instead of what is on the screen


ImaginaryHuman(Posted 2009) [#17]
i.num is not the texture handle.

You have to look at the Frames[0] array element of TIMage, and cast it to something like a TGLImageFrame or something, and then get the `name` field.

ie

MyTImage.TGLImageFrame(Frames[0]).name


slenkar(Posted 2009) [#18]
in keef.gldraw i.num is the texture handle


slenkar(Posted 2009) [#19]
For some reason the y co-ordinate has to be:

GraphicsHeight()-i.height-y


the new texture is flipped in the y direction,

do I have to create a pixmap and flip it to get it back to normal?


slenkar(Posted 2009) [#20]
your glbegun stuff doesnt make sense
if you remove glbegin and glend from the drawrect command and use this program it only draws the first rectangle
Framework keef.gldraw

initgl(800,600,0)
While Not KeyDown(KEY_ESCAPE)
Cls
glBegin(GL_QUADS)
DrawRect 0,0,200,200
DrawRect 200,200,200,200
glend
Flip
Wend



ImaginaryHuman(Posted 2009) [#21]
DrawRect calls glBegin and glEnd by itself. You should replace all glBegins with GMultiBegin and all glEnds with GMultiEnd. Right now it looks like you would be nesting them - not allowed.


slenkar(Posted 2009) [#22]
read the post again.
I said if you remove glbegin and glend from the drawrect command


MGE(Posted 2009) [#23]
With all due respect, why would people use this? It could break the nicest feature about bmax, being able to use directx on windows and opengl on other devices.


dmaz(Posted 2009) [#24]
I'd use a good enhanced opengl driver and it sounds like many of the people above would also... if there was an equal dx driver then awesome, but that's not what he's made. with opengl you already get a largely faster driver, 100% of the mac market and 100% of linux and in my sampling, 90% of the windows machines. should be 100's of millions of systems. many of the people here don't sell anything anyway so why would they care if a few old intel embedded systems can't run their stuff.

if it's -reasonable- to support both then great but there are many people who write DX only games and alienate all other systems.

In any case, who's to say this won't inspire someone to add to the dx driver, nothing's stopping them.


_Skully(Posted 2009) [#25]
Because the problem with Max2D being dual purpose is that its not optimized for either one. It has to work with both DX and OpenGL and that is a limiting factor. In order to add additional functionality it has to be supported in both GL and DX or it can't be added.

Its better to IMHO to make a DX version and an OpenGL version so that both can be optimized specific to the technology... and since OpenGL is cross platform it fits better into the Max paradigm and, again IMHO, should be the primarily supported version.


theHand(Posted 2009) [#26]
Guys, guys, I think you are missing an important point: If ImaginaryHuman and slenkar slenkar and ImaginaryHuman make many improvements to glmax2d, then it has the potential to benefit everybody (as the improvements will probably be incorporated into the official glmax2d), and BlitzMax gets that much more powerful.
To be fair, I would say that at least 60% of all the cards I've ever seen support some form of OpenGL (non-proprietary standards are the BEST!! :D ).

Honestly you two went way over my head early in the thread, so I don't want to stop you (it sounds very productive)!


ImaginaryHuman(Posted 2009) [#27]
The other thing about a multibegin/end is that there are numerous GL commands which you are not allowed to use inbetween a begin/end block. In your DrawRect function (in your code archive), commands like

DisableTex
gltranslatef(posx,posy,0)
glrotatef(rotation,0,0,1)
glloadidentity()
gltranslatef(0.375,0.375,0)

are not allowed to be used within a begin/end section. In my use of multibegin/end you have to do a test for a `begun` state prior to using any disallowed commands, ie end the previous begin if it's still unfinished, then call those commands, then begin anew. That might be what's causing the problem, because it should work otherwise. ie stick a GMultiEnd() before calling those commands. If you want to use the modelview matrix to move/position the images you're drawing, you won't be able to make use of this technique because each quad is going to have to end geometry before it can set up the modelview matrix.

Personally I like OpenGL, like many people. I see there is some reason for a benefit to supporting DirectX on Windows - for some people it's a bit faster, for some people slower, for many it's always installed more than GL, but GL isn't far behind. I think if you are going to branch off and make this new Max2D be OpenGL only, that's okay with me and many people, but some people won't like it on Windows. That said there hasn't been any easy to access presence of DirectX library features in BlitzMax - at least GL has some accessibility. Is anyone here writing their own custom DirectX code? I haven't heard of that very much at all.

Also I'm not really writing a new Max2D here, I just offered up some useful code and some ideas. I'm working on my own 2D engine so I don't really have any interest in making a second system, I'm just helping your guys out a bit.


slenkar(Posted 2009) [#28]
oh ok then maybe it can be useful for plotting points, thanks for the info


slenkar(Posted 2009) [#29]
todays addition
hollow circle and hollow rect


MGE(Posted 2009) [#30]
"many of the people here don't sell anything anyway so why would they care if a few old intel embedded systems can't run their stuff. "

Very true. Good point.


slenkar(Posted 2009) [#31]
i have embedded intel works here


dmaz(Posted 2009) [#32]
it's the older intel... ~3-4+ years. is your's that old? (I'm talking about the age of the drivers here)

[edit] and just to be safe... :) my testing wasn't exhaustive but I manage an IT dept that supports 1200 windows machines and 300 macs. the vast majority of the windows machines run intel embedded gfx. we don't keep the gfx drivers up to date so there is quite a mix... I've obviously not tested all those drivers but I ran dx7,dx9 and opengl performance tests on a lot of different computers and most (not all) of the issues were from intel gfx.

one very common thing was they would ignore waiting for vblank... that's when I decide I did need to add a timing solution. other problem for very old drivers were solid rects instead of images when using DrawImage.


ImaginaryHuman(Posted 2009) [#33]
If you want more speed you should look at using vertex arrays, they're not too difficult.. e.g. (textured irregular quads with vertex colors)... Here VertexCoordArray, VertexColorArray and TextureCoordArray are pointers to memory containing arrays of data - you could just use varptr(array[0]) for each instead. Geometry type is your GL_POINTS, GL_LINES, GL_QUADS etc. NumberOfElements is how many objects to draw. You can get 200% speedup using this versus any form of immediate mode glBegin stuff. Here vertices are four sets of 2 floats per quad. Colors are 4 unsigned bytes per vertex. Texture coords are four pairs of 2 floats per quad.

	glEnableClientState(GL_VERTEX_ARRAY)								'Switch on use of vertex array
	glEnableClientState(GL_COLOR_ARRAY)								'Switch on use of color array
	glEnableClientState(GL_TEXTURE_COORD_ARRAY)					'Switch on use of texure coordinate array
	glVertexPointer(2,GL_FLOAT,0,VertexCoordArray)				'Point to the vertex array
	glColorPointer(4,GL_UNSIGNED_BYTE,0,VertexColorArray)		'Point to the color array
	glTexCoordPointer(2,GL_FLOAT,0,TextureCoordArray)			'Point to the texture coordinate array
	glDrawArrays(GeometryType,0,NumberOfElements)						'Draw the objects!
	glDisableClientState(GL_VERTEX_ARRAY)								'Switch off use of vertex array
	glDisableClientState(GL_COLOR_ARRAY)								'Switch off use of color array
	glDisableClientState(GL_TEXTURE_COORD_ARRAY)					'Switch off use of texture coordinate array



slenkar(Posted 2009) [#34]
looks interesting,
if nothing changes between frames I can just call gldrawarrays and the same things will be drawn?
if something does change I can just change the blitz array and dont need to point to the arrays again?


you cant change the rotation and scale in between quads though?


ImaginaryHuman(Posted 2009) [#35]
Well... to an extent.

When you call glEnableClientState, it activates the use of an array for that particular aspect of data, and disables input from immediate mode for that data. So if you were to leave all three activated, you wouldn't be able to then draw a single quad with glBegin/glEnd inbetween your next use of the arrays. I think. However, if you don't plan to (the user doesn't) draw other stuff and like you say there have been no changes to the data then yes you can just call glDrawArrays and it will re-draw the whole thing without any extra setup.


slenkar(Posted 2009) [#36]
using a vertex array would mean disabling rotation and scale changes in between quads.

I like to flip my images with the scale commands.

Can you change the binded texture in vertex arrays?

todays addition

drawshaderect(x,y,width,height,r1,g1,b1,r2,g2,b2,r3,g3,b3,r4,g4,b4)


ImaginaryHuman(Posted 2009) [#37]
You can only have one texture per call to glDrawArray, so no, you can't change texture as part of the drawing.

You are also correct you can't use glRotate and glTranslate on each quad in a vertex array - that's one drawback. You could calculate the rotating/translating yourself and output a second vertex array with modified coordinates, then use that for drawing? It might still be more efficient than not using vertex arrays.


slenkar(Posted 2009) [#38]
todays addition: parent/child images

function imageparent(child:Timage,parent:Timage)
Function setimagexwhenchild(i:TImage,x)
Function setimageywhenchild(i:TImage,y)
Function setimagerotationwhenchild(i:TImage,x)


Imphenzia(Posted 2010) [#39]
@ImaginaryHuman

Your stencil functions look really interesting. I'm trying to achieve so that I draw shadows on certain images but not on the distant backgrounds but my attempts to do this with your functions are being ignored :)

1) I use GClearStencil() at the top of my loop (just as I CLS for the backbuffer)

2) I call GStencilRecord(False) before drawing my background

3) I call GStencilRecord(True) before I draw objects that I want to receive a shadow

4) I call GStencilling(True) before I draw my shadows

5) I call GStencilling(False) after I drew my shadows

This may be totally incorrect but it's what I gathered from your instructions above :)

Also, I'm just using "drawimage" and no fancy surfaces, but it works with glAlphaFunc so I suspect it should work with drawimage command for stencils as well?

Here is my attempt att combinding your functions with another test for glAlphaFunc shadows:
SuperStrict


Type TBox
	Global i:Int = 0
	Field _r:Int, _g:Int, _b:Int
	Field _x:Float, _y:Float
	Field _w:Float, _h:Float
	
	Method Update()
		_x :+ (20 * Sin(i + _y))
		i:+1
	EndMethod

	Function Create:TBox(x:Float, y:Float, r:Int, g:Int, b:Int)
		Local box:TBox = New TBox
		box._x = x
		box._y = y
		box._r = r
		box._g = g
		box._b = b
		Return box
	EndFunction

End Type

Type TBall
	Global i:Int = 0
	Field _r:Int, _g:Int, _b:Int
	Field _x:Float, _y:Float
	
	Method Update()
		_y :+ (10 * Sin(i + _x))
		i:+1
	EndMethod
	
	Function Create:TBall(x:Float, y:Float, r:Int, g:Int, b:Int)
		Local ball:TBall = New TBall
		ball._x = x
		ball._y = y
		ball._r = r
		ball._g = g
		ball._b = b
		Return ball
	EndFunction
EndType


SetGraphicsDriver GLMax2DDriver()
Graphics 800,600

' Make image to draw with
Global image:TImage = CreateImage( 100,100 )
Cls
SetColor 255,255,255
DrawOval 50,50,50,50
GrabImage image,0,0
' Make box to draw with
Global bimage:TImage = CreateImage( 100,100 )
Cls
SetColor 255,255,255
DrawRect 0,0,100,100
GrabImage bimage,0,0


Global ballList:TList = New TList
Global boxList:TList = New TList
Global shadowMode:Int = 0

' Make some objects to render
ballList.AddLast( TBall.Create( 30,150, 255,0,0 ) )
ballList.AddLast( TBall.Create( 50,150, 0,255,0 ) )
ballList.AddLast( TBall.Create( 70,150, 0,0,255 ) )
ballList.AddLast( TBall.Create( 90,150, 255,0,255 ) )
ballList.AddLast( TBall.Create( 110,150, 255,255,0 ) )

boxList.AddLast( TBox.Create( Rand(10,700), Rand(10,500), Rand(100,255), Rand(100,255), Rand(100,255)))
boxList.AddLast( TBox.Create( Rand(10,700), Rand(10,500), Rand(100,255), Rand(100,255), Rand(100,255)))
boxList.AddLast( TBox.Create( Rand(10,700), Rand(10,500), Rand(100,255), Rand(100,255), Rand(100,255)))
boxList.AddLast( TBox.Create( Rand(10,700), Rand(10,500), Rand(100,255), Rand(100,255), Rand(100,255)))
boxList.AddLast( TBox.Create( Rand(10,700), Rand(10,500), Rand(100,255), Rand(100,255), Rand(100,255)))


Function RenderBackground()
	For Local i:Int = 0 To GraphicsHeight() Step 10
		SetColor Rand(100,155), Rand(100,155), Rand(100,155)
		DrawRect 0, i, GraphicsWidth(), i + (GraphicsHeight()/10)
	Next
EndFunction

Function RenderObjectDepthTestShadows()
	' Draw shadows
	glEnable GL_DEPTH_TEST
	SetColor 0,0,0
	SetAlpha 0.3
	glEnable GL_ALPHA_TEST
	glAlphaFunc GL_GREATER, 0.25

	For Local ball:TBall = EachIn ballList
		DrawImage image, ball._x + 20, ball._y + 20
	Next
	
	glDisable GL_ALPHA_TEST
	glDisable GL_DEPTH_TEST
EndFunction

Function RenderObjectShadowsNoDepthTest()
	' Draw shadows
	SetColor 0,0,0
	SetAlpha 0.3
	For Local ball:TBall = EachIn ballList
		DrawImage image, ball._x + 20, ball._y + 20
	Next
EndFunction

Function RenderBoxes()
	SetAlpha 1.0
	
	For Local box:TBox = EachIn boxList
		SetColor box._r, box._g, box._b
		DrawImage bimage, box._x, box._y
		If Not KeyDown( KEY_SPACE )
			box.Update()
		EndIf
	Next
EndFunction


Function RenderObjects()
	SetAlpha 1.0
	
	For Local ball:TBall = EachIn ballList
		SetColor ball._r, ball._g, ball._b
		DrawImage image, ball._x, ball._y
		If Not KeyDown( KEY_SPACE )
			ball.Update()
		EndIf
	Next
EndFunction


SetBlend ALPHABLEND
glClearColor 255, 255,255,255

While Not KeyHit( KEY_ESCAPE )
	GClearStencil()
	glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
	GStencilRecord(True)
	RenderBackground()
	RenderBoxes()	
	GStencilling(True)
	If shadowMode
		RenderObjectDepthTestShadows()
	Else
		RenderObjectShadowsNoDepthTest()
	EndIf	
	GStencilling(False)
	RenderObjects()	
	
	SetColor 255,255,255
	DrawText "DepthTest/AlphaTest shadow rendering - hold SPACE to pause", 100, 10
	DrawText "Press M to toggle Depth/Alpha shadow mode on/off", 100, 30
	Flip
	
	If KeyHit( KEY_M )	
		shadowMode = 1-shadowMode
	EndIf
Wend



'GL stencil testing modes
Global GStencil:Int=False						'Whether to stencil/mask drawn pixels using the stencil buffer content
Global GStenMode:Int=GL_ALWAYS				'What GL mode To test the stencil with To determine whether To draw pixels
Global GStenThreshold:Int=0					'The stencil test reference value
Global GStenReadMask:Int=$FFFFFFFF					'A mask AND'ed with the threshold value and stencil-buffer-pixel when doing a test, to test against specific bits
Global GStenWriteMask:Int=$FFFFFFFF				'A mask AND'ed with the stencil pixel being written to write only to specific bits
Global GStenRecord:Int=False					'Whether to record new stencil content using incoming fragments
Global GStenRecordFailMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the stencil test fails
Global GStenRecordZFailMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the Z-buffer test fails but the stencil test passes
Global GStenRecordZPassMode:Int=GL_REPLACE		'What GL mode to record stencil data to the stencil buffer when the stencil test passes and the depth test fails (or is not used)
Global GBegun:Int=-1

Function GStencilRecord(DoRecording:Int=False,FailMode:Int=GL_REPLACE,ZFailMode:Int=GL_REPLACE,ZPassMode:Int=GL_REPLACE)
	'Set whether to record new drawn geometry into the stencil, and in what mode based on the result of stencil and depth tests
	'Allowed: GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, and GL_INVERT
	
	'Make sure we're not inside a glBegin-glEnd block
	If GBegun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Switch stencil recording on or off and set its mode
	If DoRecording=True
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)		'Define the test function
		glStencilOp(FailMode,ZFailMode,ZPassMode)	'Define the recording operation
		glEnable(GL_STENCIL_TEST)			'Switch on testing so that the record works, even if testing is GL_ALWAYS
		GStenRecordFailMode=FailMode			'Store mode
		GStenRecordZFailMode=ZFailMode		'Store mode
		GStenRecordZPassMode=ZPassMode		'Store mode
		GStenRecord=True					'Recording
		Return
	Else
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)		'Define the test function
		glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP)	'Define the recording operation
		If GStenMode=GL_ALWAYS Then glDisable(GL_STENCIL_TEST)		'Switch off stencilling if not required
		GStenRecordFailMode=GL_KEEP			'Store mode (if you switch stencil record off, recording mode resets)
		GStenRecordZFailMode=GL_KEEP
		GStenRecordZPassMode=GL_KEEP
		GStenRecord=False					'No recording
		Return
	EndIf
End Function

Function GStencilling(DoStencilling:Int=False,Mode:Int=GL_ALWAYS)
	'Change whether to use the stencil to restrict drawing, and in what mode

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Switch stencilling on or off and set the mode
	If DoStencilling=True
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(Mode,GStenThreshold,GStenReadMask)		'Set it
		glStencilOp(GStenRecordFailMode,GStenRecordZFailMode,GStenRecordZPassMode)		'Set the stencil op
			glEnable(GL_STENCIL_TEST)				'Enable testing
		GStenMode=Mode						'Store the mode
		GStencil=True						'Store state
		Return
	Else
		glStencilMask(GStenWriteMask)			'Set it
		glStencilFunc(Mode,GStenThreshold,GStenReadMask)		'Set it
		glStencilOp(GStenRecordFailMode,GStenRecordZFailMode,GStenRecordZPassMode)		'Set the stencil op
		If GStenRecordFailMode=GL_KEEP And GStenRecordZFailMode=GL_KEEP And GStenRecordZPassMode=GL_KEEP Then glDisable(GL_STENCIL_TEST)		'Disable testing if not required
		GStenMode=Mode					'Store the mode
		GStencil=False						'Store state
		Return
	EndIf
End Function

Function GStencilThreshold(Threshold:Int=0)
	'Set the stencil compare value - the value from the stencil at which pixels are allowed to be drawn

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the threshold
	glStencilFunc(GStenMode,Threshold,GStenReadMask)	'Set it
	GStenThreshold=Threshold					'Store the threshold
End Function

Function GStencilWriteMask(Mask:Int=$FFFFFFFF)
	'Change the bit-mask used in stencil testing to confine which bits are writeable

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the mask
	glStencilMask(Mask)						'Set it
	GStenWriteMask=Mask						'Store the mask
End Function

Function GStencilReadMask(Mask:Int=$FFFFFFFF)
	'Change the bit-mask used in stencil testing to confine which bits are readable during testing

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Set the mask
	GStenReadMask=Mask						'Store the mask
	glStencilFunc(GStenMode,GStenThreshold,GStenReadMask)	'Set it
End Function

Function GClearStencil(Value:Int=0)
	'Clear the stencil buffer to a given value
	
	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Clear it
	glClearStencil(Value)						'Set the clear value
	glClear(GL_STENCIL_BUFFER_BIT)			'Clear the stencil buffer
End Function

Function GOutput(AllowRed:Int=GL_FALSE,AllowGreen:Int=GL_FALSE,AllowBlue:Int=GL_FALSE,AllowAlpha:Int=GL_FALSE)
	'Define which of the color-buffers channels are enabled for writing output
	'Allowed GL_TRUE or GL_FALSE as parameters

	'Make sure we're not inside a glBegin-glEnd block
	If GBEgun<>-1
		glEnd()							'MUST end the geometry
		GBegun=-1							'Ended all glBegins
	EndIf

	'Define output channels
	glColorMask(AllowRed,AllowGreen,AllowBlue,AllowAlpha)		'Set it
End Function

Function GMultiBegin(Shape:Int=GL_QUADS)
	'Handle multiple calls to glBegin of the same or differnt geometry type
	'If we're already inside a glBegin of a particular type, another glBegin
	'will not be issued if Shape is the same. When Shape changes to a different
	'type of geometry, we glEnd() the current type and start a new glBegin type.
	'Be aware that only certain OpenGL commands are allowed inside a glBegin-glEnd
	'pair, so make sure to GMultiEnd() before issuing those commands.
	'Note that using GMultiBegin without GMultiEnd, geometry may not be drawn
	'until a GMultiEnd is called (like a flush command). You can make multiple calls to
	'GMultiBegin() without calling GMultiEnd() until later when you are sure you want to flush

	'Begin?
	If GBegun=-1
		'Begin a new shape
		glBegin(Shape)						'Begin a new shape
		GBegun=Shape						'We have begun this shape, need a glEnd() to follow
		Return
	Else
		'Begun already, begin a different shape?
		If GBegun<>Shape
			glEnd()
			glBegin(Shape)					'Begin a new, different shape
			GBegun=Shape					'We are beginning a new shape, need a glEnd() to follow
		EndIf
		Return
	EndIf
End Function

Function GMultiEnd()
	'To finish a block started with MultiBegin()
	'This is a substitute for glEnd() which takes into account the state of GMultiBegin
	'GMultiBegin will automatically insert a glEnd() if changing geometry type,
	'but if you need to end a geometry definition and start using commands which are
	'not allowed inside a glBegin/glEnd block, you need to issue a GMultiEnd.
	'Note that if geometry is being defined open-endedly by not calling GMultiEnd
	'very often, geometry may not be drawn until GMultiEnd is called to complete it.
	'but you can make multiple calls to GMultiBegin() and only call GmultiEnd() when
	'you want to use commands which don't work in a glBegin-glEnd block. GMultiBegin will
	'automatically call glEnd() if changing geometry type.
	
	'End?
	If GBegun<>-1
		glEnd()								'End the geometry
		GBegun=-1							'Ended all glBegins
	EndIf
End Function



ImaginaryHuman(Posted 2010) [#40]
Well, OpenGL's stencil system is a little bit convoluted and hard to grasp which is why I made a bunch of stencil functions to try to simplify it, but unfortunately there are still some quirks which catch me out.

Sometimes I think that you have to make sure you are properly specifying the stencil threshold, stencil masks, and making sure stencil testing is on when you want to record.

In your code you make a call to GStencilRecording(True) which accepts the default parameters for the other params - which is GL_REPLACE, which means the `reference value` is placed into the stencil buffer pixels. But in your code there is no definition of what the threshold should be, so it might be 0.

You also probably want to make sure the masks are set up. And you also want to explicitly set stencilling to on - which boils down to the most counter-intuitive thing about OpenGL stencils - you must have testing enabled in order to record to the stencil! It has to pass the stencil test (even if `always` passing regardless of stencil content), in order for the flow to proceed to modifying the stencil buffer.

It seems backwards because you'd think you only need to test the stencil when you want to use it to stop stuff being drawn on the screen, but it has to also be `tested` in order to record anything to the stencil. The stencil operation GL_ALWAYS in glStencilOp() (within GStencilRecording) basically says `test the stencil buffer and always allow the incoming pixel to be write the threshold value to the stencil` ... whereas it could also say something like `test the stencil buffer and only write the threshold value to the stencil if the stencil value is greater than or equal to 20`, etc. It gets a bit loopy to get your head around the flow of it.

As an example, the following five commands should allow recording to the stencil buffer. If it doesn't work, try changing the stencil threshold to 0 or change the GL_REPLACE to something else.

GStencilWriteMask($FF)
GStencilReadMask($FF)
GStencilThreshold($FF)
GStencilRecord(True,GL_KEEP,GL_KEEP,GL_REPLACE)
GStencilling(True,GL_ALWAYS)


For me, with a stencil threshold of 0 and changing GL_REPLACE to GL_INVERT allows me to definitely draw to the stencil using an `inversion` of what's already in the stencil (like a NOT operation).

I defined GStencilRecord and GStencilling separately even though they do similar things because sometimes you want to specify the system differently. But generally I think you should use GStencilling() in addition to GStencilRecord(). And it's better to define all the parameters to make sure you get what you want. The whole thing about stencil threshold and testing needs some trial and error sometimes to get it right.

Notice that GStencilling(False) doesn't necessarily switch stencil testing off! If you switch recording off it sets the stencil test operations to GL_KEEP and only then would GStencilling(False) switch off the stencil test. This is because if you are doing any kind of recording that modifies the stencil buffer it has to keep stencil testing switched on otherwise nothing will record. So you might be calling GStencilling(False) but it still keeps stencil testing on, knowing that you are still trying to record.

So in your step 4 you switch recording on and stencilling on, but then you switch stencilling off, but because you didn't switch recording off the stencil test will remain active. This probably will then carry over to the next iteration of your loop and cause something to happen that shouldn't happen, resulting in unexpected behavior. So you probably want to make sure that if you really are done with recording and you don't want to test the stencil, switch recording off first and then switch stencilling (the test) off as well.

Your step 2 is also a bit useless without also calling GStencilling(False) after it.


Rozek(Posted 2010) [#41]
Hello folks!

I just ran across this thread while looking for s.th. different - but...

are you aware of Brucey's Cairo wrapper? Cairo is an advanced 2D randering package and is used for some Linux GUIs and web page rendering. And it also renders into PDF and PS documents! With Pango it would also have professional text rendering (including right-to-left and vertical).

Thus, wouldn't it be more efficient to

- help Brucey adding GL support (e.g. not rendering into an intermediate image)
- provide an additional Pango wrapper

BlitzMAX would thus get a really professional 2D rendering facility!


slenkar(Posted 2010) [#42]
dunno how to do it


Armitage 1982(Posted 2010) [#43]
I wonder if Cairo is fast enough for doing real time stuff ?
I have see some Proof of Concept but never a complete project with good graphics using it.

A true Open GL support would be nice indeed !


Rozek(Posted 2010) [#44]
Well,

Cairo itself is already used for rendering of complete GUIs - but presumably with hardware acceleration only. For that reason, adding GL support would be fantastic!


theHand(Posted 2010) [#45]
Cairo is already( start at line 31) hardware-accelerated and I think Brucey is going to wait until it is close to stable.
Lets let the bugs and quirks be found by the Cairo developers before we start trying to use it.


Rozek(Posted 2010) [#46]
Aah,

good to know, as Cairo is a powerful package and I admire Brucey's effort to write a BlitzMAX wrapper for it! (Now there is just Pango missing)