Browse Source

tinydisplay: Add Cocoa-based backend

Adds support for tinydisplay rendering on macOS

Fixes #1285
rdb 2 years ago
parent
commit
a5fe1fa4bd

+ 9 - 1
makepanda/makepanda.py

@@ -5034,6 +5034,8 @@ if not PkgSkip("TINYDISPLAY"):
     OPTS=['DIR:panda/src/tinydisplay', 'BUILDING:TINYDISPLAY', 'X11']
     if not PkgSkip("X11"):
         OPTS += ['X11']
+    if not PkgSkip("COCOA"):
+        OPTS += ['COCOA']
     TargetAdd('p3tinydisplay_composite1.obj', opts=OPTS, input='p3tinydisplay_composite1.cxx')
     TargetAdd('p3tinydisplay_composite2.obj', opts=OPTS, input='p3tinydisplay_composite2.cxx')
     TargetAdd('p3tinydisplay_ztriangle_1.obj', opts=OPTS, input='ztriangle_1.cxx')
@@ -5044,7 +5046,13 @@ if not PkgSkip("TINYDISPLAY"):
     if GetTarget() == 'windows':
         TargetAdd('libp3tinydisplay.dll', input='libp3windisplay.dll')
         TargetAdd('libp3tinydisplay.dll', opts=['WINIMM', 'WINGDI', 'WINKERNEL', 'WINOLDNAMES', 'WINUSER', 'WINMM'])
-    elif GetTarget() != 'darwin' and not PkgSkip("X11"):
+    elif GetTarget() == 'darwin':
+        if not PkgSkip("COCOA"):
+            TargetAdd('libp3tinydisplay_tinyCocoaGraphicsWindow.obj', opts=OPTS, input='tinyCocoaGraphicsWindow.mm')
+            TargetAdd('libp3tinydisplay.dll', input='libp3tinydisplay_tinyCocoaGraphicsWindow.obj')
+            TargetAdd('libp3tinydisplay.dll', input='p3cocoadisplay_composite1.obj')
+            TargetAdd('libp3tinydisplay.dll', opts=['COCOA', 'CARBON', 'QUARTZ'])
+    elif not PkgSkip("X11"):
         TargetAdd('libp3tinydisplay.dll', input='p3x11display_composite1.obj')
         TargetAdd('libp3tinydisplay.dll', opts=['X11'])
     TargetAdd('libp3tinydisplay.dll', input='p3tinydisplay_composite1.obj')

+ 14 - 0
panda/src/tinydisplay/config_tinydisplay.cxx

@@ -12,6 +12,8 @@
  */
 
 #include "config_tinydisplay.h"
+#include "tinyCocoaGraphicsPipe.h"
+#include "tinyCocoaGraphicsWindow.h"
 #include "tinyXGraphicsPipe.h"
 #include "tinyXGraphicsWindow.h"
 #include "tinyWinGraphicsPipe.h"
@@ -79,6 +81,14 @@ init_libtinydisplay() {
 
   GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
 
+#ifdef HAVE_COCOA
+  TinyCocoaGraphicsPipe::init_type();
+  TinyCocoaGraphicsWindow::init_type();
+  selection->add_pipe_type(TinyCocoaGraphicsPipe::get_class_type(),
+                           TinyCocoaGraphicsPipe::pipe_constructor);
+  ps->set_system_tag("TinyPanda", "native_window_system", "Cocoa");
+#endif
+
 #ifdef HAVE_X11
   TinyXGraphicsPipe::init_type();
   TinyXGraphicsWindow::init_type();
@@ -116,6 +126,10 @@ init_libtinydisplay() {
 int
 get_pipe_type_p3tinydisplay() {
 
+#ifdef HAVE_COCOA
+  return TinyCocoaGraphicsPipe::get_class_type().get_index();
+#endif
+
 #ifdef _WIN32
   return TinyWinGraphicsPipe::get_class_type().get_index();
 #endif

+ 1 - 0
panda/src/tinydisplay/p3tinydisplay_composite2.cxx

@@ -1,3 +1,4 @@
+#include "tinyCocoaGraphicsPipe.cxx"
 #include "tinyGraphicsStateGuardian.cxx"
 #include "tinyOffscreenGraphicsPipe.cxx"
 #include "tinySDLGraphicsPipe.cxx"

+ 12 - 0
panda/src/tinydisplay/tinyCocoaGraphicsPipe.I

@@ -0,0 +1,12 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsPipe.I
+ * @author rdb
+ * @date 2023-03-21
+ */

+ 114 - 0
panda/src/tinydisplay/tinyCocoaGraphicsPipe.cxx

@@ -0,0 +1,114 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsPipe.cxx
+ * @author rdb
+ * @date 2023-03-21
+ */
+
+#include "pandabase.h"
+
+#ifdef HAVE_COCOA
+
+#include "tinyCocoaGraphicsPipe.h"
+#include "tinyCocoaGraphicsWindow.h"
+#include "tinyGraphicsStateGuardian.h"
+#include "tinyGraphicsBuffer.h"
+#include "config_tinydisplay.h"
+#include "frameBufferProperties.h"
+
+TypeHandle TinyCocoaGraphicsPipe::_type_handle;
+
+/**
+ * Takes a CoreGraphics display ID, which defaults to the main display.
+ */
+TinyCocoaGraphicsPipe::
+TinyCocoaGraphicsPipe(CGDirectDisplayID display) : CocoaGraphicsPipe(display) {
+}
+
+/**
+ *
+ */
+TinyCocoaGraphicsPipe::
+~TinyCocoaGraphicsPipe() {
+}
+
+/**
+ * Returns the name of the rendering interface associated with this
+ * GraphicsPipe.  This is used to present to the user to allow him/her to
+ * choose between several possible GraphicsPipes available on a particular
+ * platform, so the name should be meaningful and unique for a given platform.
+ */
+std::string TinyCocoaGraphicsPipe::
+get_interface_name() const {
+  return "TinyPanda";
+}
+
+/**
+ * This function is passed to the GraphicsPipeSelection object to allow the
+ * user to make a default TinyCocoaGraphicsPipe.
+ */
+PT(GraphicsPipe) TinyCocoaGraphicsPipe::
+pipe_constructor() {
+  return new TinyCocoaGraphicsPipe;
+}
+
+/**
+ * Creates a new window on the pipe, if possible.
+ */
+PT(GraphicsOutput) TinyCocoaGraphicsPipe::
+make_output(const std::string &name,
+            const FrameBufferProperties &fb_prop,
+            const WindowProperties &win_prop,
+            int flags,
+            GraphicsEngine *engine,
+            GraphicsStateGuardian *gsg,
+            GraphicsOutput *host,
+            int retry,
+            bool &precertify) {
+  TinyGraphicsStateGuardian *tinygsg = 0;
+  if (gsg != 0) {
+    DCAST_INTO_R(tinygsg, gsg, nullptr);
+  }
+
+  // First thing to try: a TinyCocoaGraphicsWindow
+
+  // We check _is_valid only in this case.  The pipe will be invalid if it
+  // can't contact the X server, but that shouldn't prevent the creation of an
+  // offscreen buffer.
+  if (retry == 0 && _is_valid) {
+    if ((flags & BF_require_parasite) != 0 ||
+        (flags & BF_refuse_window) != 0 ||
+        (flags & BF_resizeable) != 0 ||
+        (flags & BF_size_track_host) != 0 ||
+        (flags & BF_rtt_cumulative) != 0 ||
+        (flags & BF_can_bind_color) != 0 ||
+        (flags & BF_can_bind_every) != 0) {
+      return nullptr;
+    }
+    return new TinyCocoaGraphicsWindow(engine, this, name, fb_prop, win_prop,
+                                       flags, gsg, host);
+  }
+
+  // Second thing to try: a TinyGraphicsBuffer
+
+  // No need to check _is_valid here.  We can create an offscreen buffer even
+  // if the pipe is not technically valid.
+  if (retry == 1) {
+    if ((flags & BF_require_parasite) != 0 ||
+        (flags & BF_require_window) != 0) {
+      return nullptr;
+    }
+    return new TinyGraphicsBuffer(engine, this, name, fb_prop, win_prop, flags, gsg, host);
+  }
+
+  // Nothing else left to try.
+  return nullptr;
+}
+
+#endif  // HAVE_COCOA

+ 70 - 0
panda/src/tinydisplay/tinyCocoaGraphicsPipe.h

@@ -0,0 +1,70 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsPipe.h
+ * @author rdb
+ * @date 2023-03-21
+ */
+
+#ifndef TINYCOCOAGRAPHICSPIPE_H
+#define TINYCOCOAGRAPHICSPIPE_H
+
+#include "pandabase.h"
+
+#ifdef HAVE_COCOA
+
+#include "cocoaGraphicsWindow.h"
+#include "cocoaGraphicsPipe.h"
+#include "tinyGraphicsStateGuardian.h"
+
+/**
+ * This graphics pipe represents the interface for creating TinyPanda graphics
+ * windows on a Cocoa-based (macOS) client.
+ */
+class EXPCL_TINYDISPLAY TinyCocoaGraphicsPipe : public CocoaGraphicsPipe {
+public:
+  TinyCocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
+  virtual ~TinyCocoaGraphicsPipe();
+
+  virtual std::string get_interface_name() const;
+  static PT(GraphicsPipe) pipe_constructor();
+
+protected:
+  virtual PT(GraphicsOutput) make_output(const std::string &name,
+                                         const FrameBufferProperties &fb_prop,
+                                         const WindowProperties &win_prop,
+                                         int flags,
+                                         GraphicsEngine *engine,
+                                         GraphicsStateGuardian *gsg,
+                                         GraphicsOutput *host,
+                                         int retry,
+                                         bool &precertify);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    CocoaGraphicsPipe::init_type();
+    register_type(_type_handle, "TinyCocoaGraphicsPipe",
+                  CocoaGraphicsPipe::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "tinyCocoaGraphicsPipe.I"
+
+#endif  // HAVE_COCOA
+
+#endif

+ 12 - 0
panda/src/tinydisplay/tinyCocoaGraphicsWindow.I

@@ -0,0 +1,12 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsWindow.I
+ * @author rdb
+ * @date 2023-03-21
+ */

+ 88 - 0
panda/src/tinydisplay/tinyCocoaGraphicsWindow.h

@@ -0,0 +1,88 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsWindow.h
+ * @author rdb
+ * @date 2023-03-21
+ */
+
+#ifndef TINYCOCOAGRAPHICSWINDOW_H
+#define TINYCOCOAGRAPHICSWINDOW_H
+
+#include "pandabase.h"
+
+#ifdef HAVE_COCOA
+
+#include "tinyCocoaGraphicsPipe.h"
+#include "cocoaGraphicsWindow.h"
+#include "small_vector.h"
+
+/**
+ * Opens a window on macOS to display the TinyPanda software rendering.
+ */
+class EXPCL_TINYDISPLAY TinyCocoaGraphicsWindow : public CocoaGraphicsWindow {
+public:
+  TinyCocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
+                          const std::string &name,
+                          const FrameBufferProperties &fb_prop,
+                          const WindowProperties &win_prop,
+                          int flags,
+                          GraphicsStateGuardian *gsg,
+                          GraphicsOutput *host);
+  virtual ~TinyCocoaGraphicsWindow();
+
+  virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_flip();
+  virtual bool supports_pixel_zoom() const;
+
+  virtual void process_events();
+
+protected:
+  virtual void close_window();
+  virtual bool open_window();
+  virtual void pixel_factor_changed();
+
+private:
+  void create_swap_chain();
+  void do_present();
+
+private:
+  CGColorSpaceRef _color_space = nil;
+
+  struct SwapBuffer {
+    ZBuffer *_frame_buffer = nullptr;
+    CGDataProviderRef _data_provider = nil;
+  };
+
+  small_vector<SwapBuffer, 2> _swap_chain;
+  int _swap_index = 0;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    CocoaGraphicsWindow::init_type();
+    register_type(_type_handle, "TinyCocoaGraphicsWindow",
+                  CocoaGraphicsWindow::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "tinyCocoaGraphicsWindow.I"
+
+#endif  // HAVE_COCOA
+
+#endif

+ 278 - 0
panda/src/tinydisplay/tinyCocoaGraphicsWindow.mm

@@ -0,0 +1,278 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file tinyCocoaGraphicsWindow.mm
+ * @author rdb
+ * @date 2023-03-21
+ */
+
+#include "pandabase.h"
+
+#ifdef HAVE_COCOA
+
+#include "tinyCocoaGraphicsWindow.h"
+#include "tinyGraphicsStateGuardian.h"
+#include "tinyCocoaGraphicsPipe.h"
+#include "config_tinydisplay.h"
+
+#import <QuartzCore/CALayer.h>
+
+TypeHandle TinyCocoaGraphicsWindow::_type_handle;
+
+/**
+ *
+ */
+TinyCocoaGraphicsWindow::
+TinyCocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
+                        const std::string &name,
+                        const FrameBufferProperties &fb_prop,
+                        const WindowProperties &win_prop,
+                        int flags,
+                        GraphicsStateGuardian *gsg,
+                        GraphicsOutput *host) :
+  CocoaGraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
+  _color_space(CGColorSpaceCreateDeviceRGB())
+{
+  update_pixel_factor();
+}
+
+/**
+ *
+ */
+TinyCocoaGraphicsWindow::
+~TinyCocoaGraphicsWindow() {
+  CGColorSpaceRelease(_color_space);
+}
+
+/**
+ * This function will be called within the draw thread before beginning
+ * rendering for a given frame.  It should do whatever setup is required, and
+ * return true if the frame should be rendered, or false if it should be
+ * skipped.
+ */
+bool TinyCocoaGraphicsWindow::
+begin_frame(FrameMode mode, Thread *current_thread) {
+  begin_frame_spam(mode);
+  if (_gsg == nullptr) {
+    return false;
+  }
+
+  TinyGraphicsStateGuardian *tinygsg;
+  DCAST_INTO_R(tinygsg, _gsg, false);
+
+  tinygsg->_current_frame_buffer = _swap_chain[_swap_index]._frame_buffer;
+  tinygsg->reset_if_new();
+
+  if (mode == FM_render) {
+    // begin_render_texture();
+    clear_cube_map_selection();
+  }
+
+  _gsg->set_current_properties(&get_fb_properties());
+  return _gsg->begin_frame(current_thread);
+}
+
+/**
+ * This function will be called within the draw thread after rendering is
+ * completed for a given frame.  It should do whatever finalization is
+ * required.
+ */
+void TinyCocoaGraphicsWindow::
+end_frame(FrameMode mode, Thread *current_thread) {
+  end_frame_spam(mode);
+  nassertv(_gsg != nullptr);
+
+  if (mode == FM_render) {
+    copy_to_textures();
+  }
+
+  _gsg->end_frame(current_thread);
+
+  if (mode == FM_render) {
+    if (_swap_chain.size() == 1) {
+      do_present();
+    }
+    trigger_flip();
+    clear_cube_map_selection();
+  }
+}
+
+/**
+ * This function will be called within the draw thread after begin_flip() has
+ * been called on all windows, to finish the exchange of the front and back
+ * buffers.
+ *
+ * This should cause the window to wait for the flip, if necessary.
+ */
+void TinyCocoaGraphicsWindow::
+end_flip() {
+  if (_flip_ready) {
+    do_present();
+    _swap_index = (_swap_index + 1) % _swap_chain.size();
+  }
+
+  GraphicsWindow::end_flip();
+}
+
+/**
+ * Returns true if a call to set_pixel_zoom() will be respected, false if it
+ * will be ignored.  If this returns false, then get_pixel_factor() will
+ * always return 1.0, regardless of what value you specify for
+ * set_pixel_zoom().
+ *
+ * This may return false if the underlying renderer doesn't support pixel
+ * zooming, or if you have called this on a DisplayRegion that doesn't have
+ * both set_clear_color() and set_clear_depth() enabled.
+ */
+bool TinyCocoaGraphicsWindow::
+supports_pixel_zoom() const {
+  return true;
+}
+
+/**
+ * Do whatever processing is necessary to ensure that the window responds to
+ * user events.  Also, honor any requests recently made via
+ * request_properties()
+ *
+ * This function is called only within the window thread.
+ */
+void TinyCocoaGraphicsWindow::
+process_events() {
+  CocoaGraphicsWindow::process_events();
+
+  if (!_swap_chain.empty()) {
+    int xsize = (get_fb_x_size() + 3) & ~3;
+    int ysize = get_fb_y_size();
+
+    ZBuffer *frame_buffer = _swap_chain[0]._frame_buffer;
+    if (xsize != frame_buffer->xsize ||
+        ysize != frame_buffer->ysize) {
+      create_swap_chain();
+    }
+  }
+}
+
+/**
+ * Closes the window right now.  Called from the window thread.
+ */
+void TinyCocoaGraphicsWindow::
+close_window() {
+  if (_gsg != nullptr) {
+    TinyGraphicsStateGuardian *tinygsg;
+    DCAST_INTO_V(tinygsg, _gsg);
+    tinygsg->_current_frame_buffer = nullptr;
+    _gsg.clear();
+  }
+
+  for (SwapBuffer &swap_buffer : _swap_chain) {
+    CFRelease(swap_buffer._data_provider);
+    ZB_close(swap_buffer._frame_buffer);
+  }
+
+  CocoaGraphicsWindow::close_window();
+}
+
+/**
+ * Opens the window right now.  Called from the window thread.  Returns true
+ * if the window is successfully opened, or false if there was a problem.
+ */
+bool TinyCocoaGraphicsWindow::
+open_window() {
+  TinyCocoaGraphicsPipe *tinycocoa_pipe;
+  DCAST_INTO_R(tinycocoa_pipe, _pipe, false);
+
+  // GSG CreationInitialization
+  TinyGraphicsStateGuardian *tinygsg;
+  if (_gsg == nullptr) {
+    // There is no old gsg.  Create a new one.
+    tinygsg = new TinyGraphicsStateGuardian(_engine, _pipe, nullptr);
+    _gsg = tinygsg;
+  } else {
+    DCAST_INTO_R(tinygsg, _gsg, false);
+  }
+
+  if (!CocoaGraphicsWindow::open_window()) {
+    return false;
+  }
+
+  // Make sure we have a CALayer for drawing into.
+  _view.wantsLayer = YES;
+  _view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawNever;
+
+  create_swap_chain();
+  if (_swap_chain.empty()) {
+    tinydisplay_cat.error()
+      << "Could not create frame buffer.\n";
+    return false;
+  }
+
+  tinygsg->_current_frame_buffer = _swap_chain[_swap_index]._frame_buffer;
+
+  tinygsg->reset_if_new();
+  if (!tinygsg->is_valid()) {
+    close_window();
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Called internally when the pixel factor changes.
+ */
+void TinyCocoaGraphicsWindow::
+pixel_factor_changed() {
+  CocoaGraphicsWindow::pixel_factor_changed();
+  create_swap_chain();
+}
+
+/**
+ * Creates a suitable frame buffer for the current window size.
+ */
+void TinyCocoaGraphicsWindow::
+create_swap_chain() {
+  [_view layer].contents = nil;
+  _swap_index = 0;
+
+  for (SwapBuffer &swap_buffer : _swap_chain) {
+    CFRelease(swap_buffer._data_provider);
+    ZB_close(swap_buffer._frame_buffer);
+  }
+
+  LVecBase2i size = get_fb_size();
+
+  int num_buffers = get_fb_properties().get_back_buffers() + 1;
+  _swap_chain.resize(num_buffers);
+
+  for (SwapBuffer &swap_buffer : _swap_chain) {
+    ZBuffer *frame_buffer = ZB_open(size[0], size[1], ZB_MODE_RGBA, 0, 0, 0, 0);
+    swap_buffer._frame_buffer = frame_buffer;
+    swap_buffer._data_provider =
+      CGDataProviderCreateWithData(nullptr, frame_buffer->pbuf,
+                                   frame_buffer->linesize * frame_buffer->ysize,
+                                   nullptr);
+  }
+}
+
+/**
+ * Assigns the current framebuffer contents to the layer.
+ */
+void TinyCocoaGraphicsWindow::
+do_present() {
+  LVecBase2i size = get_fb_size();
+  SwapBuffer &swap_buffer = _swap_chain[_swap_index];
+  CGImageRef image =
+    CGImageCreate(size[0], size[1], 8, 32, swap_buffer._frame_buffer->linesize,
+                  _color_space, kCGBitmapByteOrder32Host | kCGImageAlphaNoneSkipFirst,
+                  swap_buffer._data_provider, nullptr, false, kCGRenderingIntentDefault);
+
+  [_view layer].contents = (id)image;
+  CFRelease(image);
+}
+
+#endif  // HAVE_COCOA