diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index f826530605..d62789bd38 100755 --- a/makepanda/makepanda.py +++ b/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') diff --git a/panda/src/tinydisplay/config_tinydisplay.cxx b/panda/src/tinydisplay/config_tinydisplay.cxx index b843f2e85d..f02ce5b9e8 100644 --- a/panda/src/tinydisplay/config_tinydisplay.cxx +++ b/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 diff --git a/panda/src/tinydisplay/p3tinydisplay_composite2.cxx b/panda/src/tinydisplay/p3tinydisplay_composite2.cxx index f020bbe8f1..618fe44a16 100644 --- a/panda/src/tinydisplay/p3tinydisplay_composite2.cxx +++ b/panda/src/tinydisplay/p3tinydisplay_composite2.cxx @@ -1,3 +1,4 @@ +#include "tinyCocoaGraphicsPipe.cxx" #include "tinyGraphicsStateGuardian.cxx" #include "tinyOffscreenGraphicsPipe.cxx" #include "tinySDLGraphicsPipe.cxx" diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsPipe.I b/panda/src/tinydisplay/tinyCocoaGraphicsPipe.I new file mode 100644 index 0000000000..505be66147 --- /dev/null +++ b/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 + */ diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsPipe.cxx b/panda/src/tinydisplay/tinyCocoaGraphicsPipe.cxx new file mode 100644 index 0000000000..9090b0ec16 --- /dev/null +++ b/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 diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsPipe.h b/panda/src/tinydisplay/tinyCocoaGraphicsPipe.h new file mode 100644 index 0000000000..e25aba60df --- /dev/null +++ b/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 diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsWindow.I b/panda/src/tinydisplay/tinyCocoaGraphicsWindow.I new file mode 100644 index 0000000000..4d2c2f86dc --- /dev/null +++ b/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 + */ diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsWindow.h b/panda/src/tinydisplay/tinyCocoaGraphicsWindow.h new file mode 100644 index 0000000000..961a1749c3 --- /dev/null +++ b/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 _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 diff --git a/panda/src/tinydisplay/tinyCocoaGraphicsWindow.mm b/panda/src/tinydisplay/tinyCocoaGraphicsWindow.mm new file mode 100644 index 0000000000..0bb43c4eb8 --- /dev/null +++ b/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 + +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