FB II Compiler

PG PRO

Debugging

Memory

System

Mathematics

Resources

Disk I/O

Windows

Controls

Menus

Mouse

Keyboard

Text

Fonts

Drawing

Sound

Clipboard

Printing

Communication

ASM

Made with FB

DRAWING

Beware of offscreen GWorld


I have implimented an off-screen GWorld to draw my "cluster of atoms" as was suggested by several people on the list a few weeks ago. It all works beautifully..........for awhile. After I rotatate my "cluster" 10-20 times (drawing to the off-screen GWorld each time), the program bombs or the scroll bars of the output window gets garbaged up or the output goes blank.
Subsequent attempts to run the program often gives a -108 error indicating a lack of heap space. Each time I display the drawing, I have already checked to see if the handle to the GWorld, offScreenGworld&, has the same value that it did initially and it remained unchanged. I dispose of the Gworld at the end of the program (if it hasn't crashed).
Before I send any of my code to the list, I should mention that in the drawing section I do use a mixture of toolbox _and_ FB functions. Is that all right? If not I'll change that first. My general question is when the output is set for the GWorld, is _any_ use of FB statements or functions OK.
For example could one change the current color or shade pattern with FB stuff and get away with it or does everything have to be with toolbox functions. My program works fine until it gets indigestion and craps out and I don't know whether I have a logical flaw or it is the FB stuff that is at fault. An help would be appriciated.

Eric


You need to dispose of the old gworld handle before you create a new one, otherwise you might be creating a new gworld everytime you draw it. This would lead to -108 errors (out of memory) and/or crashes. Just a thought.

As one of those suggesting Gworlds - i feel kinda responsible here, so i'll take the occasion to plunge in and make a fool of myself. -108 error? Do you have any recursive functions? That's the immediate idea that comes to mind... If so, rewrite 'em, it's the address to go back that is clogging the heap. Another possibility: how are you draw your clusters and atoms? Are you leaving handles from drawing, or ressources from cicns or similar build up in memory?

A normally set up GWorld is just a port to a window that you can't see you can say it's in RAM, or just imagine it's on that third monitor you don't happen to have. So any FB command that get's a result in a window _should_ get a result there. (I say should as cicn's are notoriously conflictual with GWorlds, even tho I've got them up and running (30fps animation) beautifully.

Write your code so that, for example, when you press the cntrl key (or not) you draw directly in the window. If it runs for hours like that, then the problem is in your GWorlds. If it bombs, then your problem is elsewhere, look at the two hints that i mentionned at first.

jonathan


Thanks for your help Jonathon: I don't have any recursive functions and I dispose of any GWorld handles created earlier from previous "clusters" I drew. The relevant code is below. To test if there is a logical error, I commented out the 15 statements in the middle (and moved up the PICTURE ON statement above the place where I do the drawing) and ran the program, not for four hours, but for a long time compared to the time to "garbage" with the 15 statements in. With the 15 statements out, the program worked fine and I couldn't make it fail. After reinstating the 15 statements, I quickly got the same problems (garbage in the scroll bars after copybits (after awhile), and then a blank screen, and eventually it bombs out).

I don't see the problem with the following code (taken from my program). Maybe I am missing something obvious. Any ideas?

Eric

I define my off-screen GWorld _once_ in an initialization FN with the following:

'First, I dispose of the previous Gworld I have used from previous "clusters".

IF offScreenGworld& THEN CALL DISPOSEGWORLD(offScreenGworld&)
portRec = wndRec 'Make GWorld rect size equal to the window rect size.
osErr = FN NEWGWORLD(offScreenGworld&,0,portRec.Top%,0,0,0)
LONG IF (osErr <> _noErr) OR (offScreenGworld& = 0)
  CLS:PRINT %(20,200);"Error forming off Screen GWorld. osErr = ",osErr
  STOP
END IF 'when things screw up, I get osErr = -108 (not enough heap space)
when I try and run the program again.

In another FN I draw to it and copy the results back to the screen.

CALL GETGWORLD(origPort&, origDev&) 'Get current port and device.
CALL SETGWORLD(offScreenGworld&,0) 'Change to off screen GWorld.

offPixMapHndl& = FN GETGWORLDPIXMAP(offScreenGworld&)
locked = FN LOCKPIXELS(offPixMapHndl&)
LONG IF locked
  CLS

'draw the cluster stuff (using toolbox _and_ FB functions)

  CALL SETGWORLD(origPort&, origDev&) 'Return port to screen.
  GET WINDOW 1,wPtr& 'get a pointer to the window
  PICTURE ON
  CALL COPYBITS(#offScreenGworld&+2,#wPtr&+2,portRec,wndRec,_srcOr,0)
  PICTURE OFF ,gCurPICT& 'Stop recording the picture.
  CALL UNLOCKPIXELS(offPixMapHndl&)
XELSE
  PRINT %(20,200);"Cannot lock offPixMapHndl&)" : STOP
END IF

In subsequent code, I use PICTURE ,gCurPICT& to display my drawing.


To the best of my knowledge, all of the FB drawing statements go through QuickDraw, which should mean that they're safe.

One thing you might watch out for, though: When your current port is set to the GWorld, FB might reset the current port to be something else (such as a screen window) "behind your back." For example, if the user clicks on a window's title bar, then FB will set the output port to that window the next time your program calls HANDLEEVENTS. A similar thing can happen if the user switches to another app and then back to your app. The result is: your program may "think" it's sending drawing commands to one port, but it's really sending them to a different one. Some of my own programs have crashed for this reason.

So, you need to take precautions, when you send a drawing command to the GWorld, to make sure that the GWorld is really your current port. I think that, in the absence of an explicit command (like WINDOW or CALL SETPORT), FB can only switch ports in this "covert" way during a HANDLEEVENTS call. Therefore, if you make sure you're set to the expected port after each call to HANDLEEVENTS, you shouldn't have this particular problem.

Rick


<< IF offScreenGworld& THEN CALL DISPOSEGWORLD(offScreenGworld&)
portRec = wndRec 'Make GWorld rect size equal to the window rect size. >>

Nope! Try "portRec;8 = @wndRec" - as is, you're only setting the first
part of portRec.

<< osErr = FN NEWGWORLD(offScreenGworld&,0,portRec.Top%,0,0,0)
LONG IF (osErr <> _noErr) OR (offScreenGworld& = 0)
CLS:PRINT %(20,200);"Error forming off Screen GWorld. osErr = ",osErr
STOP
END IF 'when things screw up, I get osErr = -108 (not enough heap space)
when I try and run the program again. >>

"run it again"... meaning you're running _within_ FB? Not a compiled app?
FB takes some of your heap space... it might just work compiled and not internal.

<< In another FN I draw to it and copy the results back to the screen.
CALL GETGWORLD(origPort&, origDev&) 'Get current port and device.
CALL SETGWORLD(offScreenGworld&,0) 'Change to off screen GWorld.
offPixMapHndl& = FN GETGWORLDPIXMAP(offScreenGworld&)
locked = FN LOCKPIXELS(offPixMapHndl&)
LONG IF locked
CLS
'draw the cluster stuff (using toolbox _and_ FB functions)
CALL SETGWORLD(origPort&, origDev&) 'Return port to screen.
GET WINDOW 1,wPtr& 'get a pointer to the window >>

Why? What do you use wPtr& for? You _shouldn't_ need it...

<< PICTURE ON
CALL COPYBITS(#offScreenGworld&+2,#wPtr&+2,portRec,wndRec,_srcOr,0) >>

Note that the line above is what displays your graphics to the screen...

<< PICTURE OFF ,gCurPICT& 'Stop recording the picture. >>

Huh???? Did you _dispose_ of the PICT resource you create in gCurPICT& each time? What on earth do you use it for, anyway? I can't imagine any situation where I'd need _both_ a gWorld copy of a picture _and_ a PICT recording of it! Especially if the PICT is just a "capture" of the bitmap as it's coming in to the screen...

<< CALL UNLOCKPIXELS(offPixMapHndl&)
XELSE
PRINT %(20,200);"Cannot lock offPixMapHndl&)" : STOP
END IF
In subsequent code, I use PICTURE ,gCurPICT& to display my drawing. >>

Hmm... Then I'm real confused. Why do you have a gWorld?

Anyway, bottom line is that if you continually create gCurPICT&'s, without disposing of them (KILL PICTURE, I think...) then you'll get exactly what you're seeing. My approach would be to fix the 8-byte rectangle move, comment out all references to gCurPICT&, and see if it doesn't do what you need. BTW; the COPYBITs back to the screen should _only_ sit in your update event handler... but get it working first, then worry about "style"!

Bill


Eric wrote:
<< IF offScreenGworld& THEN CALL DISPOSEGWORLD(offScreenGworld&)
portRec = wndRec 'Make GWorld rect size equal to the window rect size. >>

Bill Replied:
<< Nope! Try "portRec;8 = @wndRec" - as is, you're only setting the first part of portRec. >>

I hate to disagree with the expert, but providing both Rect's are DIMmed to 8 bytes, Eric's statement works perfectly. I do it all the time.

The FB Reference, p126, says, "You can assign one record to another as long as it is of the same type and size." It gives the example, "myObj = rectObject." I guarantee it works fine for rects.

Here's a simple demo:

DIM rectOne.8,rectTwo.8;0,t,l,b,r
CALL SETRECT(rectOne,100,200,300,400)
rectTwo = rectOne
PRINT t,l,b,r

=========
Eric, I'm at about the same place you are on the gWorld thing (my first, also). I'm not getting the corruption you mention on screen, but after a few runs in the debugger, I no longer have enough space to create my gWorld. I have to quit FB and start it again, which fixes it. I'll let you know if I discover anything.

I agree with Bill that the picture seems redundant, and if you're not killing it, it could account for part of your problem. When you want to draw the picture again, just COPYBITS it from the gWorld. Saves a heap of memory, and is possibly faster.

Jay


<< Eric, I'm at about the same place you are on the gWorld thing (my first, also). I'm not getting the corruption you mention on screen, but after a few runs in the debugger, I no longer have enough space to create my gWorld. I have to quit FB and start it again, which fixes it. I'll let you know if I discover anything. >>

I have to use a little bit of GWORLDs and used to have to quit after a while, now I use this statement.

CALL DISPOSEGWORLD (MyGWorld&)

So put it at the end of your code somewhere.

DIM matt
END GLOBALS

LOCAL FN mattBreak
  matt = 1
END FN

ON BREAK FN mattBreak
WINDOW 1
DO
  HANDLEEVENTS
UNTIL matt

CALL DISPOSEGWORLD (MyGWorld&)
END

Matt


<< I agree with Bill that the picture seems redundant, and if you're not killing it, it could account for part of your problem. When you want to draw the picture again, just COPYBITS it from the gWorld. Saves a heap of memory, and is possibly faster. >>

Wow - everyone got in here before I could answer! (I feel a bit concerned as I was one of the original posters suggesting GWorlds).

I'll go general:
1/if your Gworld is a fixed size then you can safely set it up at initialisation, leave it in place, and then dispose of it when leaving the app. Be careful if you leave the app during debugging by clicking on the STOP button. In this case, I think, you should also dispose of it in your break vector FN, or you're just leaving it dangling in memory. Example:

"main"
gGWorldPtr& = FN createGWorld

ON DIALOG FN doDialog

etc

DO
  HANDLEEVENTS
UNTIL gBlueInTheFace

FN myKillGWorld( gGWorldPtr&)
END

In this case you should do your drawing in the Gworld in the core parts of your app into the GWorld, then once the drawing is over, set the window to the output window and do an INVALRECT/INVALRGN. For debugging do as I sed and put in a shortcut that allows you to draw to screen rather than the GWorld, this allows you to check that your drawing is OK.
NOTE: If you have lots of clusters, you don't need to draw them all at once, you can say draw 25 atoms in each pass, and only call INVALRECT when you've finished drawing them all, this'll make your app more responsive to the user. But again, get the fondamental routines together before adding enhancements like this.

2/If the Gworld will be covering a window that can change in size then put it in your update FN instead. Example:

LOCAL FN myDoUpdate

t;8 = wndPtr& + _portRect
myGWorldPtr& = FN createGWorld( @t)

'do the drawing stuff
'do the copying stuff

FN myKillGWorld( myGWorldPtr&)
END FN

3/If you do animation with an optimised copyback rects routine then should decide on the strategy, but probably setting up 2 large GWorld at startup, drawing in them, and then copying back the changed rects, and killing at closedown, is the traditional way.

Personally I wouldn't put a copybits in the EventLoop, but in the update event. Then to provoke the copying to screen you can just do an INVALRECT, or INVALRGN, or the part where you want to draw. Even when building Regions and Gworlds on the fly, I've found that you can get very good speeds.

As to user interaction upsetting the windows - if you use technique 1 or 2 above, as any user actions are not treated while the drawing and copying is going on, they are not a problem.

The original posting was about atom clusters, and I can see 2 possibilities:
* you have a complex calculation to render the cluster, but then the same image will be used on screen for sometime.
* you have a complex calculation to render the cluster, but the image will be different each time as the user has the possibility to move atoms/clusters/rotate the thing in space.

In the first case, you can use _either_ PICTURE ON/OFF or GWorlds, not both, that's overkill. In this case either the picture or the Gworld acts as an image clipboard, so that, once the image has been calculated you can copybits (or just DRAW) it to screen each time it needs refreshing.
In this case, your GWorld should be set up at startup, then killed at the end of the app.

In the second case, this is a clear case of GWorlds, no PICTURE stuff needed. You calculate on the fly - rendering in the Gworld, and copybits back to screen as fast as you can to give the user feedback on the actions.

From your code - apart from abiguities that others may have pointed out, it's hard to determine which part is creating the error as you didn't put the FB code (that's what listmom asked anyway - so thanks for respecting listiquette). So my, and others postings can waffle and be a bit vague. From the error that you are getting, (out of heap) and the symptoms you are getting before crashing (random drawing, which implies that you're overwriting memory somewhere), and providing that your code is solid, it means that you're doing something heavy in too little space. Now, either what you're doing is right, this means that you're gonna have to increase the allocated memory, or what you're doing is wrong, and you'll have to correct it. Hope that some of this will put you on the right path.

jonathan


Many thanks to all those who responded to my GWorld question. It will take some time to digest all of Jonathan's comments (many thanks for all the details), but I appreciate everyones input. Here is a few comments about the comments.

Bill Replied:
<< Nope! Try "portRec;8 = @wndRec" - as is, you're only setting the first part of portRec. >>

Both portRec and wndRec were 8 byte records defined in the .Glbl file, so there should be no problem with portRec = wndRec. After all, that is one of the uses of records, to copy a whole lot of info with a simple equating of the record variables. Jay I think was right on this issue.

Bill (and others) also replied:
Huh???? Did you _dispose_ of the PICT resource you create in gCurPICT& each time? What on earth do you use it for, anyway? I can't imagine any situation where I'd need _both_ a gWorld copy of a picture _and_ a PICT recording of it! Especially if the PICT is just a "capture" of the bitmap as it's coming in to the screen...

No I didn't. That was the cause of the memory problem. I have used the program for months that way and never had a problem. Don't know why. I just forgot about the need to dispose of the PICT resource before making another. But now after adding the KILL PICTURE statement just prior to the new PICTURE statement, the program worked fine for an extended period of time (i.e., the memory problem is probably solved), except for one remaining strange problem. Soon after I start making and rotating the "clusters" the scroll bar region of the output window gets garbaged up (drawn with fragments of earlier "clusters", presumably from copying back from the GWorld. The heap memory problem seems to be gone.

Question:
Since the scroll bar region is an add on to the rectangle defining the output window, when the GWorld is copied back to the screen is there also a "GWorld scroll bar region" that is also copied back?, and if so, why is that region getting junk drawn into it when the GWorld and output retangles are the same? I would have thought that the GWorld rectangle doesn't have a scroll bar region and then when coping back with copibits, the scroll bar region of my regular output window shouldn't be effected. But it does get effected! Does the scroll bare region of the GWorld (if it exists) need to be "cleared" too. This is rather confusing to me.

I think I agree with Bill, Jay, and Jonathan about redundancy. I will get rid of the PICTURE and just do the copybits when I need an update. Maybe that will make the scroll bar problem go away. But I would like to know what is causing the problem.

Eric


<< Question:
Since the scroll bar region is an add on to the rectangle defining the output window, when the GWorld is copied back to the screen is there also a "GWorld scroll bar region" that is also copied back?, and if so, why is that region getting junk drawn into it when the GWorld and output retangles are the same? I would have thought that the GWorld rectangle doesn't have a scroll bar region and then when coping back with copibits, the scroll bar region of my regular output window shouldn't be effected. But it does get effected! Does the scroll bare region of the GWorld (if it exists) need to be "cleared" too. This is rather confusing to me. >>


Hi Eric,

Sounds to me as if you need to check carefully what rects you are passing to FN NEWGWORLD and to COPYBITS. I suspect you are using a rect from your window record which apparently includes the scroll bar. You may need to alter this rect to eliminate the bar from both the gWorld and the drawing area.

Unless you specifically want the image scaled, the rect you COPYBITS _from_ should be the same size as the rect you COPYBITS _to_. I have gotten some strange results by having just one pixel difference!--and for a large image it can really slow things down.

Wish I could give a more explicit description of how to fix your problem, but I'd almost bet it has to do with the rects. The existence of a scroll bar suggests to me that you are (or could be) using a gWorld larger than your viewing area, so the image can be scrolled. This makes sense, but further complicates understanding the rects involved, and could explain why you are getting interference with your scroll bar. Make sure the window rect you are COPYBITSing to doesn't cover the scroll bar, and make sure the rect you're copying from is the same size. Then it should work.

You might be interested in something I am doing, where I draw the visible portion of my image to the gWorld, copy it to the window, and set up a routine to complete the drawing (of the currently hidden parts) in the gWorld during null events. Easier said than done, but it's working great for me.

Hang in there. You'll get those atoms put together yet!

Jay


A couple of things leap out at me:

<< osErr = FN NEWGWORLD(offScreenGworld&,0,portRec.Top%,0,0,0) >>

That _is_ a typo, right? That third parameter should be the entire rect.

<< When your current port is set to the GWorld, FB might reset the current port to be something else (such as a screen window) "behind your back." For example, if the user clicks on a window's title bar, then FB will set the output port to that window the next time your program calls HANDLEEVENTS. A similar thing can happen if the user switches to another app and then back to your app. The result is: your program may "think" it's sending drawing commands to one port, but it's really sending them to a different one. >>

The fix for this one is straightforward. (Found it yesterday in IM while looking for something else... which I never did find.) If your program uses global gWorlds--set up at initialization & then kept active until quit--then _every time_ there is a refresh event, you need to route code via HANDLEEVENTS
to

FN UPDATEGWORLD(gOffPort&,0,rect,0,0,0)

Note that "refresh" events include the user resetting monitor bit depth while the app is running; this, unlike swapping windows, can actually cause the program to crash if the gWorld hasn't been properly updated. (And yes, failing to plan for this eventuality _does_ count as a bug.) If your program uses more than one window, make sure the FN only gets called when the refresh event id is the window you want to draw to. (It took me while to figure out why my moving-elevator graphics were being drawn into the elevator's button panel...)

<< could one change the current color or shade pattern with FB stuff and get away with it or does everything have to be with toolbox functions. >>

I've found one situation where color behaves differently in offscreen gWorlds than on the real screen. If you're drawing a pre-existing PICT to screen, setting the foreground color to something other than 0,0,0 will change the appearance of the PICT (nifty effect if you're doing it on purpose, appalling if it happened by accident--those of you who did "Symphony and Song" saw the effect). If you're drawing a PICT to an offscreen gWorld, it comes through in its proper color regardless.

Lucy


Lucy24 quoted the following, then replied:

<< osErr = FN NEWGWORLD(offScreenGworld&,0,portRec.Top%,0,0,0) >>

<< That _is_ a typo, right? That third parameter should be the entire rect. >>


Actually, no. :-) This is an ugly but valid FB-ism. FN NEWGWORLD doesn't actually want the value of the portRect: it wants its address. FB does a little magic for us and converts the variable you specify into an address. (This happens all throughout the toolbox.)

So passing portRec.top%, which is the first field in the record, has exactly the same effect as passing portRec. It is just a little more confusing.

The reason you see this mnemonic floating around is that under certain conditions FB won't accept a record as a parameter! You *have* to supply its field. Thus portRec.top% sneaks in.

Mars


Jay said:
<< Eric wrote:
IF offScreenGworld& THEN CALL DISPOSEGWORLD(offScreenGworld&)
portRec = wndRec 'Make GWorld rect size equal to the window rect size. >>
<< Bill Replied:
Nope! Try "portRec;8 = @wndRec" - as is, you're only setting the first part of portRec.
I hate to disagree with the expert, but providing both Rect's are DIMmed to 8 bytes, Eric's statement works perfectly. I do it all the time. >>

Hm. When did _I_ get to be "the expert"??? I flounder around just as much as anybody _else_ here!

<< The FB Reference, p126, says, "You can assign one record to another as long as it is of the same type and size." It gives the example, "myObj rectObject." I guarantee it works fine for rects. >>

I don't have FB available at the moment to check this (as you can tell by how long it's taken to respond, I'm kinda swamped...) but I _think_ the assignment works fine if the variables were both DIMmed x.8 - but _not_ if one or both were DIMmed x;8.... I know I ran into trouble with this before, as some rects need to be done one way and some the other, and changing the DIM made my assignments stop working. (Can't even remember now why the DIM had to change... which should put to rest the "expert" accusation! :-)

I also wouldn't expect it to work if one was DIMmed rect.0;t%,l%,b%,r% - FB is smart, but not _that_ smart! (I think...)

Bill


Bill wrote:
<< but I _think_ the assignment works fine if the variables were both DIMmed x.8 - but _not_ if one or both were DIMmed x;8.... >>

That sounds accurate to me. The "dot" syntax causes x to be declared as a true 8-byte variable, while the "semicolon" syntax does not. "DIM x;8" declares x as a default-type (usually 2-byte integer) variable, and forces the compiler to place the next declared variable 8 bytes from the beginning of x.

Rick


<< Hm. When did _I_ get to be "the expert"??? I flounder around just as much as anybody _else_ here! >>
Hey, don't fight it--we know on whom we can count, and it's appreciated!

<< I also wouldn't expect it to work if one was DIMmed rect.0;t%,l%,b%,r% - FB is smart, but not _that_ smart! (I think...) >>

You're right, although

DIM rect.8;0,t%,l%,b%,r%

does work! (It's what I _always_ use.) The .8 tells the compiler what size variable to record, the ;0 tells it how many bytes to allocate (overriding the 8 it would do otherwise).

(BTW, there is a typo that would keep what you wrote from working in any case: you need a comma after the 0 rather than a semicolon. Actually, I'm not sure how FB would deal with a 0-length variable, which is what DIM rect.0,t%,l%,b%,r% would indicate. The usual construction is DIM rect;0,t%,l%,b%,r% unless you're going to use what I do. It leaves rect as an integer (or default) variable.)

Here's a mnemonic (new and completely untested) that might help someone with this concept:
A "." determines the variable size--_Period_!
A ";" has a dot riding over a comma, so it _Overrides_ the allocation.

I have even had occasion to use

DIM Var1&;3,Var2&;5

so the low byte of Var1& is the high byte of Var2&. The 5 is to keep the byte count even, which the compiler requires. The fifth (last) byte is inaccessible, not part of any variable.

I don't know if this is of any use to _you_, Eric, but thanks for getting it started anyway.

Jay