diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index f9049689f..782b17c09 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -68,7 +68,7 @@ crt_machine()->set_scan_target(_scanTarget.get()); } -- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty { - _scanTarget->draw(true, (int)pixelSize.width, (int)pixelSize.height); +- (void)updateViewForPixelSize:(CGSize)pixelSize { + _scanTarget->update((int)pixelSize.width, (int)pixelSize.height); +} + +- (void)drawViewForPixelSize:(CGSize)pixelSize { + _scanTarget->draw((int)pixelSize.width, (int)pixelSize.height); } - (void)paste:(NSString *)paste { diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h index 6d6fcd42a..0fd718a27 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.h @@ -11,15 +11,24 @@ @class CSOpenGLView; +typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) { + /// Indicates that AppKit requested a redraw for some reason (mostly likely, the window is being resized). So, + /// if the delegate doesn't redraw the view, the user is likely to see a graphical flaw. + CSOpenGLViewRedrawEventAppKit, + /// Indicates that the view's display-linked timer has triggered a redraw request. So, if the delegate doesn't + /// redraw the view, the user will just see the previous drawing without interruption. + CSOpenGLViewRedrawEventTimer +}; + @protocol CSOpenGLViewDelegate /*! Requests that the delegate produce an image of its current output state. May be called on any queue or thread. @param view The view making the request. - @param onlyIfDirty If @c YES then the delegate may decline to redraw if its output would be + @param redrawEvent If @c YES then the delegate may decline to redraw if its output would be identical to the previous frame. If @c NO then the delegate must draw. */ -- (void)openGLView:(nonnull CSOpenGLView *)view drawViewOnlyIfDirty:(BOOL)onlyIfDirty; +- (void)openGLViewRedraw:(nonnull CSOpenGLView *)view event:(CSOpenGLViewRedrawEvent)redrawEvent; /*! Announces receipt of a file by drag and drop to the delegate. diff --git a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m index a458b2054..c00a2b15d 100644 --- a/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m +++ b/OSBindings/Mac/Clock Signal/Views/CSOpenGLView.m @@ -53,7 +53,15 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt - (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency { // Draw the display now regardless of other activity. - [self drawViewOnlyIfDirty:YES]; + [self performWithGLContext:^{ + [self.delegate openGLViewRedraw:self event:CSOpenGLViewRedrawEventTimer]; + CGLFlushDrawable([[self openGLContext] CGLContextObj]); + }]; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [self.delegate openGLViewRedraw:self event:CSOpenGLViewRedrawEventAppKit]; } - (void)invalidate @@ -119,19 +127,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt [self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]]; } -- (void)drawRect:(NSRect)dirtyRect -{ - [self drawViewOnlyIfDirty:NO]; -} - -- (void)drawViewOnlyIfDirty:(BOOL)onlyIfDirty -{ - [self performWithGLContext:^{ - [self.delegate openGLView:self drawViewOnlyIfDirty:onlyIfDirty]; - CGLFlushDrawable([[self openGLContext] CGLContextObj]); - }]; -} - - (void)performWithGLContext:(dispatch_block_t)action { CGLLockContext([[self openGLContext] CGLContextObj]); diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index d0833e937..d030be004 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -776,7 +776,8 @@ int main(int argc, char *argv[]) { // Display a new frame and wait for vsync. updater.update(); - scan_target.draw(true, int(window_width), int(window_height)); + scan_target.update(int(window_width), int(window_height)); + scan_target.draw(int(window_width), int(window_height)); if(activity_observer) activity_observer->draw(); SDL_GL_SwapWindow(window); } diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 2c8ed5b8f..a37408f63 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -383,10 +383,10 @@ void ScanTarget::setup_pipeline() { input_shader_->set_uniform("textureName", GLint(SourceDataTextureUnit - GL_TEXTURE0)); } -void ScanTarget::draw(bool synchronous, int output_width, int output_height) { +void ScanTarget::update(int output_width, int output_height) { if(fence_ != nullptr) { // if the GPU is still busy, don't wait; we'll catch it next time - if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, synchronous ? GL_TIMEOUT_IGNORED : 0) == GL_TIMEOUT_EXPIRED) { + if(glClientWaitSync(fence_, GL_SYNC_FLUSH_COMMANDS_BIT, 0) == GL_TIMEOUT_EXPIRED) { return; } fence_ = nullptr; @@ -666,15 +666,6 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { test_gl(glDisable, GL_BLEND); } - // Copy the accumulatiion texture to the target. - test_gl(glBindFramebuffer, GL_FRAMEBUFFER, target_framebuffer_); - test_gl(glViewport, 0, 0, (GLsizei)output_width, (GLsizei)output_height); - - test_gl(glClearColor, 0.0f, 0.0f, 0.0f, 0.0f); - test_gl(glClear, GL_COLOR_BUFFER_BIT); - accumulation_texture_->bind_texture(); - accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f); - // All data now having been spooled to the GPU, update the read pointers to // the submit pointer location. read_pointers_.store(submit_pointers); @@ -684,3 +675,18 @@ void ScanTarget::draw(bool synchronous, int output_width, int output_height) { fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); is_drawing_.clear(); } + +void ScanTarget::draw(int output_width, int output_height) { + while(is_drawing_.test_and_set()); + + // Copy the accumulation texture to the target. + test_gl(glBindFramebuffer, GL_FRAMEBUFFER, target_framebuffer_); + test_gl(glViewport, 0, 0, (GLsizei)output_width, (GLsizei)output_height); + + test_gl(glClearColor, 0.0f, 0.0f, 0.0f, 0.0f); + test_gl(glClear, GL_COLOR_BUFFER_BIT); + accumulation_texture_->bind_texture(); + accumulation_texture_->draw(float(output_width) / float(output_height), 4.0f / 255.0f); + + is_drawing_.clear(); +} diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index 8a870d957..1f2e001a5 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -42,7 +42,10 @@ class ScanTarget: public Outputs::Display::ScanTarget { void set_target_framebuffer(GLuint); - void draw(bool synchronous, int output_width, int output_height); + /*! Pushes the current state of output to the target framebuffer. */ + void draw(int output_width, int output_height); + /*! Processes all the latest input, at a resolution suitable for later output to a framebuffer of the specified size. */ + void update(int output_width, int output_height); private: #ifndef NDEBUG