Drawing to an NSView while inside an event handler - macos

I've tried everything I can think of, but no luck.
I have a project that is essentially an 'interpreter' of a programming language. So events get passed to the interpreter loop, the interpreted code does whatever (in this case, updates a memory bitmap), then the interpreter loop returns and the event handler returns. Eventually the app's drawRect gets called and the memory bitmap gets drawn to the NSView. This is all fine MOST of the time.
But... there's a few situations where the interpreted 'code' wants to cause a short animation, and does that by updating the memory bitmap, usleep()'ing for a few milliseconds, updating the memory bitmap, usleep()'ing, etc. The animation takes a little less than a second, so the thread-blockage shouldn't be an issue.
The problem is that NONE of the animation shows, and the screen doesn't get updated until after the interpreted 'code' ends, and the event returns.
The sleep function that gets invoked when the interpreted code indicates it wants to sleep looks like this:
void KSleep(DWORD tm) {
if( [pView lockFocusIfCanDraw] ) {
inSleep = true;
[pView setNeedsDisplay:YES];
[pView display];
[pView drawRect:NSMakeRect(0, 0, pView.frame.size.width, pView.frame.size.height)];
[pView unlockFocus];
}
usleep(tm*1000);
}
'inSleep' is a global variable I set up for testing purposes, 'pView' is a global NSView* to the window's only view. NOTE: Yes, some of the above code is redundant, I'm just including it to show that I've tried numerous combinations of trying to indicate to the OS that the view is dirty and to update it. None of them have worked.
The drawRect code (removing all of the code that does the run-of-the-mill blitting of the bitmap) looks like this:
-(void)drawRect:(CGRect)rect {
CGContextRef context = [[NSGRaphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
if( inSleep ) CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
else CGContextSetRGBFillColor(context, 1.0, 1.0, 0.0, 1.0);
CGContextFillRect(context, CGRectMake(0,0,200,200));
CGContextRestoreGState(context);
}
So:
1) The -mouseDown() event occurs and it calls the interpreter with the event.
2) The interpreted code draws to the bitmap (I'm ignoring that here, as it's not important to the working or not working of the screen update), and invokes 'sleep.'
3) The interpreter, seeing the 'sleep' invocation, calls Ksleep() (above).
4) Ksleep locks the focus, which seems to create a context, as without that, the debugger spit out warnings of a 0x0 context during the drawRect() function, and with the the lockFocus it does not and appears to have a valid context value.
5) Ksleep flags the view as needing an update and calls (varyingly) 'display' and/or 'drawRect', etc.
6) The drawRect routine DOES get control (breakpoints indicate that everything is working okay in that regard). 'inSleep' IS set correctly. It steps through everything in drawRect as expected. But NOTHING shows on the display, until...
7) drawRect returns to Ksleep, the sleep times out, the interpreter keeps interpreting, the interpreted code does more drawing and more sleeps, about 10 times (thus repeating steps 2-7 about 10 times).
From the time the program starts until the mouse action causes the 'animation' attempt, a yellow rectangle is drawn in the view. Once the 'animation'-causing mouse click occurs, NOTHING gets updated in the window until the animation completely finishes (even though drawRect IS executing multiple times throughout that animation attempt), THEN the rectangle turns blue. But breakpoints show that execution IS passing through the drawRect (with 'inSleep' true) each time the KSleep() routine is called.
Is this a thread thing? (The program is not explicitly creating any threads.)
I'm not particularly looking for suggestions for how to avoid the animation/KSleep structure, I realize that's not the preferred Macos method of doing things, but this is an attempt to port an old project from elsewhere, and modifying the 'interpreted' code to avoid this is not a possibility.
Thanks for any ideas or suggestions.

The solution I found was to place ALL of the interpreter code into a second thread, freeing up the main thread to be able to update the display. Even though the drawRect routine was running and drawing, it wasn't getting flushed to the display until after the current "command interpretation" cycle completed and returned. This page has a fair amount of information on the subject:
https://developer.apple.com/library/content/technotes/tn2133/_index.html
Additionally, all of the 'invalidate' calls/statements had to be saved/buffered into a common "smallest rectangle that includes all of the invalidates", and then that invalidate done AFTER the "command interpretation" returned AND/OR before a sleep() was done.
PITA.

Related

Updating contents of multiple NSTextView objects in a single operation

The database application I am working on can have a window with multiple NSTextView elements for displaying and editing data. When the current spot in the database is repositioned, all of the NSTextView objects in the window need to be updated with new contents. This is done with a loop that scans each object and checks to see if it needs to be updated. If it does, the new value is calculated, then updated by using the [NSTextView setString:] method. Here is a simplified version of the code involved.
for formObject in formObjectsInWindow {
NSTextView * objectTextView = [formObject textView];
NSString * updatedValue = [formObject calculateValue];
[objectTextView setString: updatedValue];
}
This works, but if there are a lot of objects, it is somewhat slow. Probably related, the display does not update all at once, you can actually see a "ripple" as the objects are updated, as illustrated in this movie (this movie has been slowed down to 1/4 speed to make the ripple effect more pronounced, but it is definitely visible at full speed).
If you've gotten this far, you might suspect that the calculateValue method is slow, but that isn't the problem. In other places the same code is used and runs at tens of thousands of operations per second. Also, this delay only occurs during update operations, it doesn't occur when the window is first opened, even though the same calculations are required at that time. Here is an example. Notice that when I switch back to the detail view all the NSTextView objects update instantaneously, even though the record changed and all of the values are different.
My suspicion is that the [NSTextView setString:] method is updating the off-screen buffer, then immediately copying that to the on-screen buffer, so that this double buffering is happening over and over again for each item, causing the delay and ripple. If so, I'm sure there must be some way to prevent this so that the screen is only updated at the end after all of the values have been updated. It's probably something simple that I am missing, but I'm afraid I am stumped as to how this is supposed to be done.
By the way, this application does not use layer-backed views, and is not linked against the QuartzCore framework.
I brought up this question with Apple engineers at the WWDC 2018 labs. It turns out the problem is that the setString: method does not mark the NSTextView object as needing display. The system does eventually notice that the text has changed and updates the display, but this happens in an asynchronous process, hence the "ripple" effect. So the workaround is simply to add a call to setNeedsDisplay after calling setString.
[objectTextView setString: updatedValue]
[objectTextView setNeedsDisplay:YES];
Adding this one line of code fixed the problem, no more ripple effect.
I'm told that this is actually a bug, so hopefully this extra line won't be needed in future versions of macOS. As requested, a radar has been filed (41273611 if any Apple engineers are reading this).

My code isn't enough to show a triangle

The codes below is in winmain function after registering a class for the parent window:
RECT disrect;
HWND stat = CreateWindow("BUTTON","abcdef",
WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,10,150,500,100,dis,0,0,0);
HDC hdc=GetDC (stat);
FillRect(hdc,&disrect,CreateSolidBrush(RGB(3,5,54)));
SetTextColor(hdc,RGB(25,250,250));
POINT p[3];
p[1].x=280;
p[1].y=280;
p[2].x=280;
p[2].y=290;
p[3].x=285;
p[3].y=285;
Polygon(hdc,p,3);
TextOut(hdc,10,10,"hhhhh",5);
But when I run it, only shows a white rectangle into the parent window, neither the rect is filled with black brush, nor there is any text in it.
Could you guys tell me where I am wrong?
Unless you want to display animations, you should never try to directly write to a window that way, because many events could cause the window to redraw itself erasing what you have just written.
The correct way is to put it in the WM_PAINT handler.
A few issues, in addition to not using WM_PAINT.
First, merely calling CreateSolidBrush() is not enough to mark that brush as the one for your drawing operations to use. You have to select the brush into a DC (device context) before you can use it. This is done with the SelectObject() function. General usage looks like
HBRUSH prevBrush;
prevBrush = SelectObject(hdc, newBrush);
// drawing functions
SelectObject(hdc, prevBrush);
Yes, it is important to restore the previous brush when finished, even on a fresh DC; the initial state must be restored. The initial state uses a brush that draws nothing; this is why your Polygon() doesn't draw anything. SelectObject() is used for all the various things you use to draw with (pens, fonts, etc.), not just brushes.
Second, in C array indices start at 0 and go to size - 1, not start at 1 and go to size. So instead of saying pt[1], pt[2], and pt[3], you say pt[0], pt[1], and pt[2]. Your compiler should have warned you about this.
Third, as the documentation for CreateSolidBrush() will have said, once you are finished with the brush you must destroy it with DeleteObject(). You must do this after selecting the previous brush back in. You must also do this with the brush you used in the FillRect() call.

How do I detect "invalid drawable" in Mac OS X?

I am working on a cross platform application that is over a decade old. The UI is by Qt and backend rendering is done with OpenGL. OpenGL contexts are managed in the back end, not by Qt.
I have recently added error checking and reporting for all OpenGL code in our app. Occasionally a situation arises where the first render initiated by Qt causes an "invalid drawable" error message in the terminal and all subsequent OpenGl calls fail with an "invalid framebuffer" error reported. These invalid drawable error messages have been treated as innocuous in the past, since before the user sees it the drawable eventually becomes valid and the scene is rendered correctly. However, with the new OpenGL error check/report it's not possible since there are large numbers of errors reported.
I would like to test if the drawable is valid. If it is not, it should return before the render starts. How can I verify that the drawable is valid?
MacBook Pro, OS X Mountain Lion (10.8.3), ati graphics card
I don't know at what API level you're working. I'm not sure it's possible to detect the problem after the fact. That is, if all you have is a context (perhaps implicit as the thread's current context) that failed to connect to its drawable.
I presume that Qt is using Cocoa under the hood. I further assume it has created an NSOpenGLContext and is invoking -setView: on it. You get that "invalid drawable" error if, at the time of that call, the view's window doesn't have a window device.
One common technique is to defer setting the context's view until the view has -drawRect: called on it, since at that point you're sure that the view has a window and the window has a device. (Although that ignores the possibility of forced drawing outside of the normal window display mechanism. For example, -cacheDisplayInRect:toBitmapImageRep:.)
If you just want to know at the point of the call to -setView: whether it's safe or not, I think you can rely on checking the value of [[view window] windowNumber]. The docs for -windowNumber say:
If the window doesn’t have a window device, the value returned will be equal to or less than 0.
Another approach is to prevent the problem rather than detect it. The strategy for that is basically to make sure the window has been shown and drawn before the call to -setView:. You may be able to force that by ordering it on-screen and invoking -display on it.
Ken Thomases post gave me the basic info I needed to find a workable solution. An additional conditional was needed. Here's what worked
//----------------------------------------------------------------------------
bool vtkCocoaRenderWindow::IsDrawable()
{
// you must initialize it first
// else it always evaluates false
this->Initialize();
// first check that window is valid
NSView *theView = (NSView*)this->GetWindowId();
bool win =[[theView window] windowNumber]>0;
// then check that the drawable is valid
NSOpenGLContext *context = (NSOpenGLContext *)this->GetContextId();
bool ok = [context view] != nil;
return win && ok;
}

Refresh a NSOpenGLView within a loop without letting go of the main runloop in Cocoa

I am building an Cocoa/OpenGL app, for periods of about 2 second at a time, I need to control every video frame as well as writing to a digital IO device.
If after I make the openGL calls I let go of the main thread (like if I make the openGL calls inside a timer fire-method with an interval of like 0.01 Sec) openGLview is refreshed with every call to glFinish().
But If I instead keep the main thread busy like in a 2 second long while loop, openGl calls won't work (surprisingly the first call to glFinish() would work but the rest won't).
The documentation says that glFinish should block the thread until the gl commands are executed.
Can anybody please help me understand what is going on here or provide a solution to this problem.
To make it clear, I want to present 200 frames one after another without missing a frame and marking each frame refresh by writing to a digital IO port (I don't have a problem with this) all on Snow Leopard.
This is not quite my department - pretty vanilla NSOpenGLView user myself - but from the Mac OpenGL docs it looks like you might want to use a CVDisplayLink (Q&A1385) for this. Even if that won't do it, the other stuff there should probably help.
EDIT
I've only done some basic testing on this, but it looks like you can do what you want as long as you first set the correct OpenGL context and then swap buffers after each frame (assuming you're using a double buffered context):
// inside an NSOpenGLView subclass, somewhere outside the usual drawing loop
- (void) drawMultipleFrames
{
// it might be advisable to also do a [self lockFocus] here,
// although it seems to work without that in my simple tests
[[self openGLContext] makeCurrentContext];
// ... set up common OpenGL state ...
for ( i = 0; i < LOTS_OF_FRAMES; ++i )
{
// ... draw your frame ...
glFinish();
glSwapAPPLE();
}
// unlockFocus here if locked earlier
}
I previously tried using [[self openGLContext] flushBuffer] at the end of each frame instead -- that doesn't need glSwapAPPLE but doesn't block like glFinish so you might get frames trampling over one another. This seems to work OK with other apps, runs in the background etc, but of course YMMV.

How can I refresh core plot without returning from a method?

I have a cocoa interface that uses core plot. When I press a button in the interface, a plot is drawn. I wanted to create a sequence of graphs by calling the plotting method multiple times along with calls to sleep() in between. But it seems that even though the call to reload data is made that nothing happens until the function exits(only showing last graph as well). Now I know that CPAnimation exists but before I start using it I was wondering what it is that happens when the function exits that makes the graph refresh. Would I have to yield to the thread that takes care of the refreshing instead of using sleep?
Ok I figured out how. I called the reloadData method from a method in a separate thread (which always returns). This boiled down to calling reloadData from an IBAction and also with an NSTimer. Finally instead of using sleep I will use NSConditionLock coordinate the processing and the refreshing
Presumably, Core Plot (or your code) sets the view as needing display. That doesn't happen immediately; it happens when you return to the event loop.
Whenever you use sleep in a Mac OS X application, you kill a puppy. Use NSTimer instead. Have your timer callback method do the work required for one graph, and set whatever instance variables are necessary for your the method to know which graph it should draw, so that the method draws each graph in turn until it runs out of them.
Or, better yet, present a list of graphs that the user can choose from, instead of making the user watch all the graphs as a slide-show. (Unless an explicitly-labeled slide-show is what you're implementing.)
Core Plot, like most Cocoa drawing frameworks, is lazy: it draws at the end of the run loop iteration. This is to ensure that things aren't drawn too often.
Rather than drawing immediately, the layers are marked as needing drawing.
As was pointed out by others, a better approach to sleeping is to use NSTimer to avoid blocking the run loop, or to use NSObject methods like performSelector:withObject:afterDelay:
Peter has it right—the reload data method doesn't actually draw anything. The plot is marked as needing display and refreshed the next time the layers are drawn to the screen. If you use sleep, it never gets a chance to draw.
Also note that Core Plot is a fairly young project; CPAnimation and the related classes are stubs. They don't really do anything yet. :-)

Resources