changing pixmaps within an image

BlitzMax Forums/BlitzMax Programming/changing pixmaps within an image

PantsOn(Posted 2007) [#1]
Hi

I understand that we have to use pixmap = lockimage(image) and unlockimage(image) to change the pixels in an image, but I was wondering why?

This should work, but doesn't and was wondering why we can't change values in memory using poke or writepixel if we haven't locked the image first.

an example (doesn't do anything though)
i:timage = createimage(100,100)

writepixel i.pixmaps[0],0,0,$ffffffff


while (in theory) this does work
i:timage = createimage(100,100)
p:tpixmap = lockimage(i)
writepixel p,0,0,$ffffffff
unlockimage i



Having a look through max2d.bmx etc I notice that its not doing much when locking an image just passing back a pointer to the i.pixmaps[frame]

I'm very confused at unlocking an image as the code doesn't seem to do much!??!!? Theres nothing in the function...
Rem
bbdoc: Unlock an image
about:
Unlocks an image previously locked with #LockImage.
end rem
Function UnlockImage( image:TImage,frame=0 )
End Function



PantsOn(Posted 2007) [#2]
Ok strange one...
after playing around with the code abit.. I found out that if you set the i.seqs[0]=0 the image is updated.

i:timage = createimage(100,100)

writepixel i.pixmaps[0],0,0,$ffffffff

i.seqs[0] = 0


The UnlockImage command still seems a bit pointless?!!?!?
Now to find out what the seqs[] value means....


tonyg(Posted 2007) [#3]
Lockimage does not additional initialisation including a check whether a pixmap is associated to the image yet which CreateImage does not do.



<edit> Yep, unlock is redundant.
<edit> I *think* seq is set if there is currently a graphics context (e.g. a graphics command).


PantsOn(Posted 2007) [#4]
seqs[] benchmark...

Strict

Graphics 800,600,0

Local i:Int = 0
Local im:timage = CreateImage(100,100)
Local m:Int

m = MilliSecs()
While i < 10000
	DrawImage im,0,0
'	im.seqs[0] = 0
	
	i:+1
Wend

Print MilliSecs()-m


With the im.seqs[0] line rem'd out, I get 15 millisecs.
With the im.seqs[0] in, I get 6793 millisecs.
Ouch!

what does the seqs[0]=0 do?
Does it re-dump the image into graphics mem or something?


PantsOn(Posted 2007) [#5]
looks like it does do some memory stuff...
if seqs[] = graphicsSeq then it returns the already created frame.
if different, it creates a frame from the pixmap data.
Wish it could be quicker though.

	Method Frame:TImageFrame( index )
		If seqs[index]=GraphicsSeq Return frames[index]
		frames[index]=_max2dDriver.CreateFrameFromPixmap( Lock(index,True,False),flags )
		If frames[index] seqs[index]=GraphicsSeq Else seqs[index]=0
		Return frames[index]
	End Method



klepto2(Posted 2007) [#6]
I have postet some direct pixmap access code yesterday in another topic. http://www.blitzbasic.com/Community/posts.php?topic=72178

It currently only works within opengl but is a lot faster than doing it with lockImage because bmax doesn't update each mipmap. ALso it seems ( I haven't found out why) that if you lock an Image bmax seems to update the pixmap each time you access a pixel. Mine definetly only updates the Image if you call the UploadPixtoImg() function. Also it is possible to just update parts of the image so it is again much faster.

cheers.


PantsOn(Posted 2007) [#7]
i saw your stuff klepto2 when I posted my MPEG player.
As its only OpenGL, can stuff still be cross-platform?

I like my code not to force someone to use windows or opengl etc...


klepto2(Posted 2007) [#8]
OpenGL is the crossplattform way. The directx driver is windows only (unfortunatly also set as default).
The code will be crossplattform. I will try to make also a directx version but therefor I have to look under the dx hood ;).


tonyg(Posted 2007) [#9]
If you're looping through the whole pixmap changing pixels I wonder if its quicker to write the new data to memory and then do a memcopy once you've finished.


PantsOn(Posted 2007) [#10]
tonyg : it should only update the image when you go to draw the image. You should be able to write as many pixels as you want. It only slows down when drawing after a change.

Klepto2 : Cool.. I will update my code accordingly. How do I detect if the graphics have been set to OpenGL?


tonyg(Posted 2007) [#11]
it should only update the image when you go to draw the image.

Yes but doesn't it use seperate method calls to write each pixel back to the existing pixmap on at a time?


PantsOn(Posted 2007) [#12]
From what I can see from the code..
You can make as many writepixel calls to the pixmap.
Only when you draw it.. it will call a single function that converts the whole pixmap to a drawable frame. What this function does I'm not sure.

But you've got me thinking...
What if we have a huge image type that represents an image of 4000*4000.
This type contains 400*400 smaller images of 10*10. When you write pixel anything to this huge image (it will write to the correct image in the type), when it comes to drawing it will only have to convert the nessecary 10*10 images, rather than the whole 4000*4000 pixels.
This could be a huge saving in speed.
I'm going to play arounf with this tonight.


tonyg(Posted 2007) [#13]
You can make as many writepixel calls to the pixmap.


Yep, for a 256*256 pixmap that's 65536 calls.
What write the pixel data to a memory block and then use 1 call to memcopy all the dat in one go?

it will call a single function that converts the whole pixmap to a drawable frame

Yep. I agree.


ImaginaryHuman(Posted 2007) [#14]
You definitely don't want to try converting the entire pixmap into an image.

When you lock the image you are saying to the system that you want the handle to the image to be kept and not lost and that you want to change the image. If you change it, it HAS to be re-uploaded in full to the graphics card via the graphics bus. It will not detect the changes in the image to optimize the upload, it just sends the whole thing. The transfer from the pixmap in main memory to the image in video ram is what is causing the delay - and you should avoid having to make that transfer as much as possible. A pixmap is in main memory and cannot be displayed by the graphics driver, which is why you have to upload it whenever you've made some changes. However, like you said, if you compose your game world from various smaller images, each with its own smaller pixmap, then you only have to upload (when you unlock the image) the pixmaps that contain changes (ie mark them as dirty etc). You should also be only uploading enough images that are required to display a full screen of the game world - you do not need to hold the entire 4000x4000 texture data in video ram at all times, only the parts that are needing to be displayed, and you should then have the changing areas or the areas which are coming into view be reuploaded via unlocking those image. You have to use a pool of generic dummy images so you'll need to indirectly reference them.

To change the pixmap, use either readpixel/writepixel or use pointers in combination with PixmapPtr()

It is a good idea to make the higher-level multi-image type that you suggested. Then when you need to write to a given pixel it will abstractify the coordinates and write it into the appropriate pixmap.

It is also good to bear in mind that the maximum texture/image size has a limit which varies based on the graphics card. Some older cards can only do up to 256x256 while more recently they go to like 2048x2048 or more. Ideally you'd select image sizes which are the maximum texture size or something smaller, but that obviously means that then your pixmaps have to be divided up into different sized chunks based on the image dimensions.


PantsOn(Posted 2007) [#15]
Hi ImaginaryHuman


To change the pixmap, use either readpixel/writepixel or use pointers in combination with PixmapPtr()


Thats what i do, unfortunately i want to draw them as images.
Image are faster at drawing and contain alpha, but converting pixmaps to the image is very slow.

So I need a fast way to draw aplha'd pixmaps.
hmmmmm


Dreamora(Posted 2007) [#16]
Paste is the only way to put a pixmap into a pixmap.
You can not alter images without using render to texture.


klepto2(Posted 2007) [#17]
PantsOn:



ImaginaryHuman(Posted 2007) [#18]
The difference between a pixmap and an image is in what area of memory the data is stored, and what processor is being used to access it for rendering.

A pixmap in main memory can only be `drawn` to the display by the CPU. The data has to always transfer over the graphics bus which is somewhat slower than the CPU speed. The data is read and written by the CPU from main memory to video memory. This is always a relatively slow process because the BUS is slow.

An image is in video ram - somewhere that the graphics processor can `see`, so that it can read it as a texture and write it out as a visible image. It can do that really quickly and usually much faster than the CPU. BUT, this system is not designed to allow you to easily alter the texture that is being drawn.

The focus over the years has been really strongly on getting things to DRAW faster and hardly any focus on getting things to READ/CHANGE faster. Usually reading the contents of graphics and changing them is considered a non-realtime activity, since the main function of the GPU is to draw stuff and make it display.

The video ram is faster for the GPU to access since physically it is usually located close to the GPU and is not part of the main memory chips, and usually runs at a different speed. Video ram is kind of a `cache`, similar to a CPU cache, whereby things that are put into video ram can be accessed by the GPU most quickly, whereas things that need to be fetched from main memory (that are not cached), such as transferring new pixmap data, is always going to be slower. It's the same with CPU's, accessing stuff that is in the cache is fast, stuff outside the cache is as slow as accessing main memory with the CPU. Pixmaps are that slow offline memory and Images are the fast cached memory. You cannot and will not be able to find a way to make pixmaps as fast as images. The GPU is much faster at drawing pixels from an image, than the CPU is at transferring the pixels from main memory.

That said, in recent years additional features have been added to OpenGL to support things like render-to-texture, using at texture as a second back-buffer so that you can use the GPU to draw `changes` to it and then keep those changes for the future - so then you just draw the changed texture rather than having to keep transferring it from the pixmap. But BlitzMax does not support this by standard, you have to get into using GL extensions and writing your own GL code.

There is no fast way to draw an alpha pixmap. If you are going to insist on using a pixmap you have to at some point pay a time penalty for transferring it from main memory to video memory. It is faster to transfer it straight into a `texture` (image) and to let the GPU draw it to the screen, than to draw it directly to the backbuffer. Even the fastest drawing of a pixmap is usually more than 10 times slower than getting the GPU to draw it. The GPU is highly optimized to draw a huge number of pixels from `images` in a short time. Drawing a pixmap is a pretty archaic way to do things. Forget about it.

Focus on transferring as little of the changed pixmap to a set of images as infrequently as possible.


tonyg(Posted 2007) [#19]
.... or check out the Render-To-Texture threads. HighGfx managed it and Indiepath's module might still be changeable to do it. I *still* can't believe Mark hasn't included it yet.h


PantsOn(Posted 2007) [#20]
an update to klepto2's function with opengl checking

Function UpdatePixmapInImage:Int(image:TImage,frame:Int=0)
	
	' if not opengl, use the standard blitzmax way of updating the pixmap frame
	If TGLMax2DDriver(_max2dDriver) = Null Then
		' directx maybe
		image.seqs[frame]=0
		Return True
	EndIf
	
	' open GL
	' based on klepto2 version of quick upload of pixmap to frame
	Local pixmap:TPixmap
	pixmap = image.pixmaps[frame]
	
	Local ImgF:TGLImageFrame = TGLImageframe(image.frame(frame))
	Local mip_level:Int = 0

	If ImgF <> Null Then
		glBindtexture GL_TEXTURE_2D,ImgF.name
		glPixelStorei GL_UNPACK_ROW_LENGTH,pixmap.pitch/BytesPerPixel[pixmap.format]
		glTexSubImage2D GL_TEXTURE_2D,mip_level,0,0,pixmap.width,pixmap.height,GL_RGBA,GL_UNSIGNED_BYTE,pixmap.pixels
		glPixelStorei GL_UNPACK_ROW_LENGTH,0
		glBindtexture GL_TEXTURE_2D,0
	EndIf
	
	Return True
End Function