Optimize speed of Picture to RGBA MemoryBlock algorithm

I’m not at my computer, but there’s a memory block function that takes a color or is that a binary stream? Check the LR.

If you’re doing Cocoa, you don’t need the mask RGBSurface, look at the color.alpha channel.

The other way, which is more complicated. Is to use declares to creat a CGBitmapContext, draw the picture into the context and then extra the raw pixel data.

Alwyn, that revised code looks to me about as optimized as one can get without using plugins, nice job.

Also, I agree with your Feature Request from February : <https://xojo.com/issue/24259> It would be nice if Picture.GetData() had some raw data formats to choose from.

[quote=36529:@Sam Rowlands]I’m not at my computer, but there’s a memory block function that takes a color or is that a binary stream? Check the LR.

If you’re doing Cocoa, you don’t need the mask RGBSurface, look at the color.alpha channel.[/quote]

Thanks Sam, I’ll look into these.

If the images are known at app start then the fastest will be to precompute the MemoryBlock data and load it from string constants or a file.

I took a stab at declares and while this works there’s a lot it’s not accounting for. It just so happens that a Picture created as “new Picture(w, h)” is returning data in ARGB format but I don’t know if that’s always the case or how to coerce it if not. There’s a CGBitmapInfo object that describes it’s format which isn’t being checked here. This doesn’t work in Carbon, format is different somehow and the circle is stretched.

[code]Structure CFRange
location As integer
length As integer
End Structure

Function pictureToRGBAMemoryBlock(pic As Picture) As MemoryBlock

#if not TargetCocoa then
return nil
#endif

dim w As integer = pic.Width
dim h As integer = pic.Height
dim byteCount As integer = w * h * 4

//=========== declare to underlying bytes
soft declare function CGImageGetDataProvider lib “ApplicationServices” (img As Ptr) As Ptr
soft declare function CGDataProviderCopyData lib “ApplicationServices” (prov As Ptr) As Ptr
soft declare sub CFDataGetBytes lib “ApplicationServices” (CFDataRef As Ptr, range As CFRange, m As Ptr)
soft declare sub CFRelease lib “CoreFoundation” ( obj as ptr )

dim cgImage as Ptr = pic.CopyOSHandle(Picture.HandleType.MacCGImage) //get handle (needs release)

dim dataProv As Ptr = CGImageGetDataProvider(cgImage) //get provider (dont release)

dim dataRef As Ptr = CGDataProviderCopyData(dataProv) //get dataref (needs release)

dim range As CFRange //range of bytes to read
range.location = 0
range.length = byteCount

dim mem As new MemoryBlock(byteCount + 1) //make destination memory (+1 byte more)

CFDataGetBytes(dataRef, range, mem) //read range of bytes into memory

CFRelease(cgImage) //release those ptrs
CFRelease(dataRef)

//========== mem is now in ARGB format, scan A to other side for RGBA (why mem is 1 byte more)
dim memPtr As Ptr = mem
dim idx1 As integer = byteCount
dim idx2 As integer = idx1 - 4
while idx2 >= 0
memPtr.Byte(idx1) = memPtr.Byte(idx2)
idx1 = idx2
idx2 = idx2 - 4
wend

//========= return MemoryBlock that’s shifted over 1 byte so it skips that first unused A
return Ptr(Integer(memPtr) + 1)

End Function

dim p As new Picture(256, 256)
p.Graphics.ForeColor = &cFF0000
p.Graphics.FillOval 0, 0, 256, 256

dim mem As MemoryBlock = pictureToRGBAMemoryBlock§

//stuff mem into OpenGL as RGBA format
[/code]

When using declares, you would have to write a different PicToRGBA function for each different OS though. But if it speeds things up significantly it shouldn’t be too difficult to wrap them all in a parent method that uses compiler directives to decide which function to call.

Ensuring that code is usable all platforms is one of my main goals, but declares definitely opens the door to faster processing.

I would suggest using the plugin. That’s much more direct and less copying.
The declare code above makes 4 copies in memory before getting the values.
Also I would not count that data provider of a CGImage is in raw bytes and not a JPEG.

For one image I tested, Alwyns optimized method ran in 53ms, and even with 4 copies the declares ran in 1ms. So if done right that’d be way fast.

I guess CopyOSHandle must be making a copy of all the data, not just some pointer, and with a plugin you can bypass even that. I’ve made a few simple plugins before, if you’re willing to tackle declares I think you could make a very direct single function plugin uber-optimized for building OpenGL formats.

Wow, didn’t realize declares would make such a huge difference… now I’ll have to rethink everything :wink:

Looks great to me, the only thing I would say that you do need to validate that the data provider is accessible (double check the pointer) and you need to check the format (I don’t recall off the top of my head), but the data might be a different byte order and it may but UInt32, Uint32 or floats.

You may be even able to pass the OpenGL declares the CFData object directly (if it’s in the right format), otherwise I would suggest using the CGBitmapContext route, as you can specify channel orders and bitsPerChannel. This should then save having to manually convert the data.

[quote=36594:@Christian Schmitz]I would suggest using the plugin. That’s much more direct and less copying.
The declare code above makes 4 copies in memory before getting the values.
Also I would not count that data provider of a CGImage is in raw bytes and not a JPEG.[/quote]

Alwyn has already stated that he wants to avoid the use of plug-ins if possible as he is working on an open source 3D Engine so unless you are prepared to open source your plug-in or at least allow free use it is not helping to talk about MBS plug-ins as a solution. If the ultimate speed gains are only available through the plug-in system then we should help Alwyn to produce his own so that he can follow his open source mission.

I must say well done Stuart, 53ms to 1ms is huge if that kind of optimisation is achievable then I can’t really see how a plug-in can make a meaningful difference given the required use anyway.

The results from Alwyn’s Xojo-only method and Stuart’s declares-based method should be compared, just to make sure they are identical.

By the way, if you wanted to incorporate MacOSLib into the project, these methods are built-in and the code would be something like this:

  dim cgi as CGImage = CGImage.NewCGImage( p )
  dim m as MemoryBlock = cgi.DataProvider.CopyData.Data

When I try to run the declares-based method on my Mac Mini (Xojo R1… haven’t updated my Mac yet), I get a “There is no class with this name: CFRange”. What am I doing wrong?

Looks like it was a Xojo structure created by Stuart to pass to the functions?

You missed the Structure that has to be set up at the top of that code:

Structure CFRange
  location As integer
  length As integer
End Structure

[quote=36698:@Kem Tekinay]You missed the Structure that has to be set up at the top of that code:
[/quote]

I think Alwyn has been working too hard :slight_smile:

Hmmm, must have been on of my blonde moments…

Probably, but luckily it doesn’t feel like work when one is having fun…

Ok, so I’ve dusted off my Mac Mini and tested the optimized vs the declares-based method, with a larger image than the one I used in the previous tests:

Optimized: 430ms
Declares: 222ms

The texture’s color wasn’t 100% with the declares method, but I think the result still is a clear indication that declares is the way to go for best performance.

This suggests that the results of the two methods were different. Did you do a byte comparison?

Byte comparison?