Need help with slightly modified Scroll SDK example

I’m almost done with my pet BrightScript project (a folder-based comic viewer). I am able to generate a list of image files on an external drive then display them to the Screen one by one by using the Fast Forward and Reverse keys on the Roku Remote. The problem is i can only go through about half of the image files before i get an error stating that ‘bigbm’, the roBitmap being displayed on the screen, is invalid. I think it’s because i’m using a GOTO call to change the image file sent to ‘bigbm’. I need a fresh set of eyes to point me in the right direction on this. If my method is misguided i would appreciate any suggestions you all might have. I am aware this might be too much code to have you all look through. Giving it a shot anyways because i’m stumped!


' ******
' ****** Scroll a view around a large plane, double buffered
' ******

Library "v30/bslCore.brs"

Function IsHD()
    di = CreateObject("roDeviceInfo")
    if di.GetDisplayType() = "HDTV" then return true
    return false
End Function

sub main()
    viewComic("ext1:/Comics/Batman/Batman 001")
end sub

function viewComic(path as string)

    'Check if the screen is in HD or not.
    if IsHD()
        screen=CreateObject("roScreen", true, 1280, 720)  'try this to see zoom
    else
        screen=CreateObject("roScreen", true)
    endif
    
    ImageIndex = 0
    imagefiles = matchfiles(path,"*.jpg")
    
    
NewFile:
    filepath = path + "/" + imagefiles[ImageIndex]
    print filepath
    
    'Load comic page with jpg of comic.
    bigbm=CreateObject("roBitmap", filepath)
    'Check if comic was unable to be created.
    if bigbm = invalid
        print "bigbm create failed"
        stop
    endif
    
    'Set background region to that of the image loaded in bigbm.
    backgroundRegion=CreateObject("roRegion", bigbm, 0, 0, screen.getwidth(), screen.getheight())
    'Check if background region was unable to be loaded.
    if backgroundRegion = invalid
        print "create region failed"
        stop
    endif
    
    'Prevent image from overscrolling.  Stops at image edges.
    backgroundRegion.SetWrap(false)

    'Output image to screen.
    screen.drawobject(0, 0, backgroundRegion)
    screen.SwapBuffers()
    
    msgport = CreateObject("roMessagePort")
    screen.SetPort(msgport)
    
    movedelta = 16
    if (screen.getwidth() <= 720)
        movedelta = 8
    endif

    codes = bslUniversalControlEventCodes()

    pressedState = -1 ' If > 0, is the button currently in pressed state
    while true
	if pressedState = -1 then
	    msg=wait(0, msgport)   ' wait for a button press
	else
	    msg=wait(1, msgport)   ' wait for a button release or move in current pressedState direction 
	endif
        if type(msg)="roUniversalControlEvent" then
                keypressed = msg.GetInt()
                print "keypressed=";keypressed
                if keypressed=codes.BUTTON_UP_PRESSED then 
                        Zip(screen, backgroundRegion, 0,-movedelta)  'up button pressed
			            pressedState = codes.BUTTON_UP_PRESSED 
                else if keypressed=codes.BUTTON_DOWN_PRESSED then 
                        Zip(screen, backgroundRegion, 0,+movedelta)  ' down button pressed
			            pressedState = codes.BUTTON_DOWN_PRESSED 
			    else if keypressed=codes.BUTTON_RIGHT_PRESSED then 
                        Zip(screen, backgroundRegion, +movedelta,0)  ' right button pressed
                        pressedState = codes.BUTTON_RIGHT_PRESSED 
                else if keypressed=codes.BUTTON_LEFT_PRESSED then 
                        Zip(screen, backgroundRegion, -movedelta, 0)  ' left button pressed
                        pressedState = codes.BUTTON_LEFT_PRESSED 
                else if keypressed = 8 then
                        print "Rewind"
                        if ImageIndex = 0 then 
                           'Ignore reverse page request if at end of imagelist.
                        else
                            ImageIndex = ImageIndex - 1
                            GOTO NewFile
                        end if 
                else if keypressed = 9 then
                        print "Fast Forward" 
                        if ImageIndex = ImageFiles.count() then
                            'Ignore forward page request if at end of imagelist.
                        else
                            ImageIndex = ImageIndex + 1
                            GOTO NewFile
                        end if
                else if keypressed=codes.BUTTON_BACK_PRESSED then
		                pressedState = -1
		                exit while
                else if keypressed=codes.BUTTON_UP_RELEASED or keypressed=codes.BUTTON_DOWN_RELEASED or keypressed=codes.BUTTON_RIGHT_RELEASED or keypressed=codes.BUTTON_LEFT_RELEASED then 
		                pressedState = -1
                end if
	else if msg = invalid then
                print "eventLoop timeout pressedState = "; pressedState
                if pressedState=codes.BUTTON_UP_PRESSED then 
                        Zip(screen, backgroundRegion, 0,-movedelta)  'up button released
                else if pressedState=codes.BUTTON_DOWN_PRESSED then 
                        Zip(screen, backgroundRegion, 0,+movedelta)  ' down button released
                else if pressedState=codes.BUTTON_RIGHT_PRESSED then 
                        Zip(screen, backgroundRegion, +movedelta,0)  ' right button released
                else if pressedState=codes.BUTTON_LEFT_PRESSED then 
                        Zip(screen, backgroundRegion, -movedelta, 0)  ' left button released
		end if
        end if
    end while

end function

function Zip(screen, region, xd, yd)
    region.Offset(xd,yd,0,0)    
    screen.drawobject(0, 0, region)
    screen.SwapBuffers()
end function

I have found that a Roku 1 is a little more sensitive to memory allocation issues: you have to be careful in what order you allocate and de-allocate large bitmaps. I’m not sure that’s what is going on with your code, though.

I have also found that you can only allocate/create so many bitmaps on any model before the call to create a bitmap will fail (return invalid). You probably need to do some kind of garbage collection and/or destroy your roScreen and create a new one.

Good luck!
-JT

I believe the key is invalidating the bitmap and the region before creating the new one. In your code, you’re creating a new bitmap without invalidating the previous one, so the memory from the previous bitmap is still going to be in use when you try creating the new one. You should also invalidate the region before creating the new bitmap, because it will hold the reference to the previous bitmap open, resulting in the same issue.

Add the following lines immediately after your “NewFile:” label and see if it helps:

bigbm = invalid
backgroundRegion = invalid

“TheEndless” wrote:
I believe the key is invalidating the bitmap and the region before creating the new one. In your code, you’re creating a new bitmap without invalidating the previous one, so the memory from the previous bitmap is still going to be in use when you try creating the new one. You should also invalidate the region before creating the new bitmap, because it will hold the reference to the previous bitmap open, resulting in the same issue.

Add the following lines immediately after your “NewFile:” label and see if it helps:

bigbm = invalid
backgroundRegion = invalid

I added those two lines of code and unfortunately it still has the same problems. Debug screen either says the bigbm create failed or or backroundRegion failed.

You probably need to do some kind of garbage collection and/or destroy your roScreen and create a new one.
What kind of statements do i need to use to do some garbage collection, just set the object to invalid? I tried something called RunGarbageCollector() but that didn’t help. I’m sure there has got to be a way to do this, roSlideshow is able to go through 25+ image pages without memory error. It would have been even better if roSlideShow supported scrolling, i wouldn’t need to create my own. :grinning_face_with_smiling_eyes:

The last time I had problems with running out of memory, I did a few experiments. It seemed that simply setting a variable to invalid didn’t accomplish anything. Running the garbage collector also didn’t help much. This was based on printing out the results of RunGarbageCollector(). What did seem to help was to let my roScreen go out of scope completely. Running the garbage collector after that also may have helped, but I don’t remember if I tried it both ways.

What I ended up with was creating an underlying roImageCanvas in my main routine and then calling a function that created an roScreen and everything else. After I had created numerous bitmaps and at an opportune time, I’d return back to my main routine, thus allowing the roScreen to close and go out of scope, and then run the garbage collector. Then I’d call the function that created the roScreen again and recreate everything. It’ not perfect. If you run my app long enough it will still run out memory. Based on the output of RunGarbageCollector() there are still things that are not getting deallocated, but I felt it was good enough and I didn’t bother to try to figure out why things I wasn’t using anymore weren’t getting cleaned up.

I’m not saying that the roScreen is the problem since obviously anything created in the routine that created the roScreen also goes out scope when the routine returns back to main. I’m using many bitmaps and regions.

Obviously, I don’t fully understand what’s going on with object allocation/deallocation. That’s kind of why I said, “Good luck!”. If you figure out anything I’d love to hear about it.

-JT

JT, for what it’s worth, my aquarium screensaver never lets the roScreen go out of scope, and it fully refreshes itself (loads all new bitmaps, 30+) every 15 minutes without issue. It does, however, ensure that all other bitmaps and regions go completely out of scope before re-loading, which is why I suggested that might be the issue. It also uses the roCompositor for most of them, so it could be that it has better memory management built into it.

TL;DR Try allocating the msg port right after allocating the roScreen and outside the loop.

This may help in keeping memory from being fragmented.

Here is the reference guide on Garbage Collection. Objects are de-allocated when they are assigned invalid; more precisely: when their reference count goes to zero. RunGarbageCollection() is only necessary to remove circular references. I also thought about setting to invalid (as TheEndless suggested) but wasn’t sure in the original statement if the de-allocation occurred before the allocation or vice versa. Setting to invalid certainly removes the ambiguity.

In my experience, the order of allocations / de-allocations make a difference. I presume because of memory fragmentation. Take care to de-allocate everything you’ve allocated since the bitmap. In the code, you are allocating a msgport after the bitmap. And it’s being allocated/de-allocated every loop.

I’ve tried de-allocating just about everything that can be de-allocated. I just tried to make it more simple by using an array of roRegions and it looks like it can only do 4 items before saying the bitmap ‘bmImage’ was unable to be created. I find that to be a bit odd that it would stop working that early into it.

UPDATED CODE:


' ******
' ****** Scroll a view around a large plane, double buffered
' ******

Library "v30/bslCore.brs"

Function IsHD()
    di = CreateObject("roDeviceInfo")
    if di.GetDisplayType() = "HDTV" then return true
    return false
End Function

sub main()
    viewComic("ext1:/Comics/Wonder Woman/Wonder Woman 002")
end sub

function viewComic(path as string)
    'Check if the screen is in HD or not.
    if IsHD()
        screen=CreateObject("roScreen", true, 1280, 720)  'try this to see zoom
    else
        screen=CreateObject("roScreen", true)
    endif
    
    
    imagefiles = matchfiles(path,"*.jpg")
    ImageIndex = 0
    
    print imagefiles.count()
    backgroundRegion = [imagefiles.count()]
    
    msgport = CreateObject("roMessagePort")
    screen.SetPort(msgport)

    for i = 0 to imagefiles.count() - 1
        print imagefiles[i]
        bmImage = invalid
        bmImage = CreateObject("roBitmap", path + "/" + imagefiles[i])
        if bmImage = invalid then
            print "bmImage is invalid"
        end if
        backgroundRegion[i]=CreateObject("roRegion", bmImage, 0, 0, screen.getwidth(), screen.getheight())
        if backgroundRegion[i] = invalid then
            print "create region failed"
        end if
    end for
        
NewFile:
    
    'Set background region to that of the image loaded in bigbm.
    'backgroundRegion=CreateObject("roRegion", bigbm, 0, 0, screen.getwidth(), screen.getheight())
    'Check if background region was unable to be loaded.

    
    'Prevent image from overscrolling.  Stops at image edges.
    backgroundRegion[ImageIndex].SetWrap(false)

    'Output image to screen.
    screen.drawobject(0, 0, backgroundRegion[ImageIndex])
    screen.SwapBuffers()
    screen.finish()
    
    movedelta = 16
    if (screen.getwidth() <= 720)
        movedelta = 8
    endif

    codes = bslUniversalControlEventCodes()

    pressedState = -1 ' If > 0, is the button currently in pressed state
    while true
	if pressedState = -1 then
	    msg=wait(0, msgport)   ' wait for a button press
	else
	    msg=wait(1, msgport)   ' wait for a button release or move in current pressedState direction 
	endif
        if type(msg)="roUniversalControlEvent" then
                keypressed = msg.GetInt()
                print "keypressed=";keypressed
                if keypressed=codes.BUTTON_UP_PRESSED then 
                        Zip(screen, backgroundRegion, 0,-movedelta)  'up button pressed
			            pressedState = codes.BUTTON_UP_PRESSED 
                else if keypressed=codes.BUTTON_DOWN_PRESSED then 
                        Zip(screen, backgroundRegion, 0,+movedelta)  ' down button pressed
			            pressedState = codes.BUTTON_DOWN_PRESSED 
			    else if keypressed=codes.BUTTON_RIGHT_PRESSED then 
                        Zip(screen, backgroundRegion, +movedelta,0)  ' right button pressed
                        pressedState = codes.BUTTON_RIGHT_PRESSED 
                else if keypressed=codes.BUTTON_LEFT_PRESSED then 
                        Zip(screen, backgroundRegion, -movedelta, 0)  ' left button pressed
                        pressedState = codes.BUTTON_LEFT_PRESSED 
                else if keypressed = 8 then
                        print "Rewind"
                        if ImageIndex = 0 then
                            'Ignore forward page request if at end of imagelist.
                        else
                            ImageIndex = ImageIndex - 1 
                            GOTO NewFile
                        end if
                else if keypressed = 9 then
                        print "Fast Forward" 
                        if ImageIndex = ImageFiles.count() then
                            'Ignore forward page request if at end of imagelist.
                        else
                            ImageIndex = ImageIndex + 1 
                            GOTO NewFile
                        end if
                else if keypressed=codes.BUTTON_BACK_PRESSED then
		                pressedState = -1
		                exit while
                else if keypressed=codes.BUTTON_UP_RELEASED or keypressed=codes.BUTTON_DOWN_RELEASED or keypressed=codes.BUTTON_RIGHT_RELEASED or keypressed=codes.BUTTON_LEFT_RELEASED then 
		                pressedState = -1
                end if
	else if msg = invalid then
                print "eventLoop timeout pressedState = "; pressedState
                print "ERROR: msg is Invalid"
                if pressedState=codes.BUTTON_UP_PRESSED then 
                        Zip(screen, backgroundRegion, 0,-movedelta)  'up button released
                else if pressedState=codes.BUTTON_DOWN_PRESSED then 
                        Zip(screen, backgroundRegion, 0,+movedelta)  ' down button released
                else if pressedState=codes.BUTTON_RIGHT_PRESSED then 
                        Zip(screen, backgroundRegion, +movedelta,0)  ' right button released
                else if pressedState=codes.BUTTON_LEFT_PRESSED then 
                        Zip(screen, backgroundRegion, -movedelta, 0)  ' left button released
		end if
        end if
    end while

end function

function Zip(screen, region, xd, yd)
    region.Offset(xd,yd,0,0)    
    screen.drawobject(0, 0, region)
    screen.SwapBuffers()
end function

I’m not sure if this would be applicable, but there was a post stating that the maximum size image that can be loaded was reduced to 2048x2048 - perhaps one of your images is larger than that?
What happens if you shuffle or randomize the images - does it always fail for you on the same number in the queue? What do the failing images have in common?

Hmm…I just looked through the dimensions of the images and they are mostly under 2000px by both height and width, even the ones the channel stops at. Also, it didn’t matter which comic i was using. Both my Batman 001 comic and my Wonder Woman 002 stop after about 4 images being loaded into the array. I took out the “STOP” command in my code and the debug screen shows the for loop continues to execute but does not have valid roBitmaps after the first four.

Ignore the lower sentences, it doesn’t work with random images from Google Images.
If you’re curious you could try the updated code out using a folder with 10 random images found on Google Images or something. The only code that would need to be changed is the file path i hard-coded into the viewComic() call.

How big are the first five images (the four that works and the one that fails)? Generally there is memory for a “few” screen-sized images, but the number decreases the larger the images get. Since you mentioned they are “mostly” under 2000px (which is pretty huge), I guess your images are pretty big?

More to the point, is there any reason to have more than one of these bitmaps instantiated at one time? Why are you keeping an array of them?

–Mark

Oh, i was just doing it for testing purposes. I still have the original code that goes through images one by one. I wanted to see if it would still stop working even though there would be no re-allocation going on. Yes, they are pretty big - they hover around the 1900px range.

First 5 images are 1280x1966, 1280x1966, 1280x1966, 2000x1537, 1280x1966. The one that doesn’t work is also 1280x1966. I just tried the channel using 5 random images that are mostly around 700px and none of them worked. Yet the images from the comics have at least the first 4 that are able work with bigger dimensions. Maybe the Roku just isn’t meant for this kind of app? :disappointed_face: Which is a shame because the images look pretty sweet on my big screen. :mrgreen:

Loading that many large images simultaneously is probably maxing-out the available video memory.

Is there a reason you need more than one screen-sized bitmap in memory at a time? I would maybe load two, and when you load the third, de-allocate the first one.

  • Joel

Another option - I realize you’re loading from USB/external - but you could probably get away with copying 100 of those images into the tmp storage area of the roku and then they should swap in and out of ram quicker that way one at a time. If you don’t mind the initial copying delay process. Rather than scrolling the images, you could also try chopping each page into individual frames with a left/right control sequence for next and previous frame to reduce the loading and rendering time.

Looking at the comments, I had to review your code again. I didn’t see at first where in the code you are retaining the bitmaps in memory. Being able to load only 4 or 5 is indicative of retaining the bitmaps in memory. It’s also indicative of allocating larger and larger bitmaps with intervening objects that are not deallocated. The first step is to stop retaining both the bitmaps and the regions. Although I can’t say exactly what the memory requirements of a region is, I have tested bitmaps. Here’s the post where I relate my experience on memory limits and bitmaps.

If you’re allocating large and increasing chunks, I could easily see it taking up memory from fragmentation, see the diagram below.

Note that in this diagram, bitmap4 is bigger than bitmap3, which is bigger than bitmap2, which is bigger than bitmap1. Any allocation that is not freed (represented by the |x|) causes fragmentation.


|<---bitmap1--->|x|<-----free ------------------------------------------------------>|        AFTER ALLOCATING bitmap1
|<---free ----->|x|<---bitmap2------->|x|<-----free -------------------------------->|        AFTER DEALLOCATING bitmap1 and ALLOCATING bitmap2
|<---free ----->|x|<---free --------->|x|<---bitmap3--------->|x|<-----free -------->|        AFTER DEALLOCATING bitmap2 and ALLOCATING bitmap3
|<---free ----->|x|<---free --------->|x|<---free ----------->|x|<---bitmap4----------->|     FAILS

Perhaps the allocation of |x| is not obvious. The two examples of fragmenting allocations that are in the code segment you provided are:
msgport = CreateObject(“roMessagePort”)
screen.SetPort(msgport)

and

codes = bslUniversalControlEventCodes()

Move both of these code segments to before the NewFile label. And remove array of regions.

Then let us know how that works!

I’m not sure cropping would work right since it would cut off the text balloons at odd places. I tried saving the images to the tmp: drive but could only get one to be saved there so i scrapped that. I think i’m just bad at programming. :laughing:

Umm…i think my Roku is doing voodoo or something, haha. I rearranged the image file names surrounding, and including, the image file that throws the invalid bitmap error to “z11.jpg, z12-13.jpg, and z14.jpg” instead of the original “11.jpg, 12-13.jpg, and 14.jpg” so that they would appear at the bottom of the list of images in the directory. Somehow the Roku device automatically rearranged them again to the order they are supposed to be in while keeping the zXX.jpg file name. Is that normal for this system, arranging files from right to left maybe?

Screenshot of what i’m talking about(telnet session):
http://i.imgur.com/k7ngM.jpg

Edit: MSGreg, i’ll look into that. I didn’t see your post until just now. I guess i’ll take a breather and just enjoy the Roku for the time being.

I think that would be normal. When you rename a file it remains in the same location in the file allocation table of the device the file resides in. If you are sorting the read filenames in an array, then Z11.jpg is still further down the sorted list than 1.jpg since numbers come first.